diff --git a/build.sbt b/build.sbt index f4f738fbbf6..600a3ff05b4 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/core/src/main/scala/cats/arrow/NaturalTransformation.scala b/core/src/main/scala/cats/arrow/NaturalTransformation.scala index 336fc92a106..87a96e92fcd 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/core/src/main/scala/cats/data/Const.scala b/core/src/main/scala/cats/data/Const.scala index 2219498b0b3..736400de01f 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/core/src/main/scala/cats/data/OptionT.scala b/core/src/main/scala/cats/data/OptionT.scala index 913a6a55955..b683b571085 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/core/src/main/scala/cats/data/StreamingT.scala b/core/src/main/scala/cats/data/StreamingT.scala index ec5c7500433..76b2a9cf08f 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/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index 69d5608df2c..2ae18aa3e9d 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -178,6 +178,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{ @@ -187,6 +202,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 @@ -228,6 +249,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 @@ -295,4 +322,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/core/src/main/scala/cats/data/WriterT.scala b/core/src/main/scala/cats/data/WriterT.scala index 093e4d2c274..dd967357295 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 } @@ -151,6 +185,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/core/src/main/scala/cats/data/Xor.scala b/core/src/main/scala/cats/data/Xor.scala index 19a98fa8d9a..ac5892198be 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/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 5bc5669bbe9..6bc5c4a23c4 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -22,6 +22,22 @@ 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 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))) @@ -277,10 +293,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] diff --git a/core/src/main/scala/cats/std/function.scala b/core/src/main/scala/cats/std/function.scala index 85e33b4ca45..8d7c3af2d7b 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 { +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 = @@ -71,13 +71,41 @@ 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 } + + implicit val function1MonoidK: MonoidK[Lambda[A => A => A]] = + new Function1MonoidK {} +} + +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 } + + implicit val function1SemigroupK: SemigroupK[Lambda[A => A => A]] = + new Function1SemigroupK {} +} + +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 => + B.combine(x(a), y(a)) + } +} + +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] 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] sealed trait Function1MonoidK extends MonoidK[Lambda[A => A => A]] with Function1SemigroupK { + override def empty[A]: A => A = identity[A] } trait FunctionInstances diff --git a/docs/src/main/tut/apply.md b/docs/src/main/tut/apply.md index 49968a0968d..6607ab928e6 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 @@ -139,8 +140,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.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 629e5558688..32e4c075b96 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, ?]] = @@ -239,7 +239,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) @@ -271,7 +271,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 e707b216660..34ff817440c 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/freeapplicative.md b/docs/src/main/tut/freeapplicative.md index be14c4d17d2..fb605ad00f4 100644 --- a/docs/src/main/tut/freeapplicative.md +++ b/docs/src/main/tut/freeapplicative.md @@ -5,10 +5,176 @@ 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 +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:silent +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)) + } + } +``` + +```tut +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")) + } + } + +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? +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 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 +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) diff --git a/docs/src/main/tut/freemonad.md b/docs/src/main/tut/freemonad.md index 46e95b4d657..ad85d63e4d6 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, ~>} @@ -448,7 +447,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/docs/src/main/tut/functor.md b/docs/src/main/tut/functor.md index a25c589cc66..612bdbbd4d4 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/invariant.md b/docs/src/main/tut/invariant.md new file mode 100644 index 00000000000..cda5dbf20f6 --- /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 +``` diff --git a/docs/src/main/tut/kleisli.md b/docs/src/main/tut/kleisli.md index f9119cb1621..58b09436315 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 a751480d9df..84664d00045 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 8ff8f37f7c0..e9f9b6020f8 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/oneand.md b/docs/src/main/tut/oneand.md index 42cf7e5cd3d..d38cc4fab8e 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] +``` diff --git a/docs/src/main/tut/semigroup.md b/docs/src/main/tut/semigroup.md index a23ef66c793..1cd396efaf9 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 d47a1b3bacb..0f3e837883a 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 f4de95ce4a1..eb6b8e448db 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 e698e8a325c..5084b1c5b25 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 238a4cf556a..fa047cfdb52 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, ?]] = @@ -207,7 +209,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 @@ -218,7 +220,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, @@ -232,7 +238,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, ?]] = @@ -249,7 +255,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] @@ -298,16 +304,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 3c676706d92..80b2099b6a7 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" diff --git a/free/src/main/scala/cats/free/Free.scala b/free/src/main/scala/cats/free/Free.scala index 7acd6be79ab..585463ade0e 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. */ @@ -116,14 +124,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`. * diff --git a/free/src/test/scala/cats/free/FreeTests.scala b/free/src/test/scala/cats/free/FreeTests.scala index 146111061d0..d43ccebcbc6 100644 --- a/free/src/test/scala/cats/free/FreeTests.scala +++ b/free/src/test/scala/cats/free/FreeTests.scala @@ -5,21 +5,12 @@ import cats.arrow.NaturalTransformation import cats.tests.CatsSuite import cats.laws.discipline.{MonoidalTests, MonadTests, SerializableTests} import cats.laws.discipline.eq._ +import cats.laws.discipline.arbitrary.function0Arbitrary import org.scalacheck.{Arbitrary, Gen} +import Arbitrary.{arbitrary, arbFunction1} 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._ implicit val iso = MonoidalTests.Isomorphisms.invariant[Free[Option, ?]] @@ -31,4 +22,65 @@ class FreeTests extends CatsSuite { x.mapSuspension(NaturalTransformation.id[List]) should === (x) } } + + ignore("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)) + } +} + +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 { + 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(freeGen[F, A](6)) + + 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/free/src/test/scala/cats/free/InjectTests.scala b/free/src/test/scala/cats/free/InjectTests.scala index 7a83165beec..0bd4ff45d01 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)) diff --git a/laws/src/main/scala/cats/laws/SerializableLaws.scala b/laws/src/main/scala/cats/laws/SerializableLaws.scala index 7d505c97152..38d3284fc89 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() diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index 1c285ee726e..ec5da2481bb 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -108,9 +108,6 @@ object arbitrary extends ArbitraryInstances0 { 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)) - 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]), @@ -119,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/scripts/travis-publish.sh b/scripts/travis-publish.sh index 538abaeed15..d74cdf07993 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" diff --git a/state/src/test/scala/cats/state/StateTTests.scala b/state/src/test/scala/cats/state/StateTTests.scala index 5dc9a8c37cf..06c646d1cc0 100644 --- a/state/src/test/scala/cats/state/StateTTests.scala +++ b/state/src/test/scala/cats/state/StateTTests.scala @@ -3,6 +3,7 @@ package state import cats.tests.CatsSuite import cats.laws.discipline.{MonoidalTests, MonadStateTests, MonoidKTests, SerializableTests} +import cats.free.FreeTests._ import cats.laws.discipline.eq._ import org.scalacheck.{Arbitrary, Gen} @@ -39,19 +40,71 @@ 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])) + 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) + } + } + + { + 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])) + } + + { + implicit val iso = MonoidalTests.Isomorphisms.invariant[State[Long, ?]] + 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/CokleisliTests.scala b/tests/src/test/scala/cats/tests/CokleisliTests.scala index 9baf71d421f..bdab63439c9 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._ @@ -29,6 +29,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] diff --git a/tests/src/test/scala/cats/tests/ConstTests.scala b/tests/src/test/scala/cats/tests/ConstTests.scala index 1a73ce66d8f..c52482425a4 100644 --- a/tests/src/test/scala/cats/tests/ConstTests.scala +++ b/tests/src/test/scala/cats/tests/ConstTests.scala @@ -32,6 +32,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, @@ -43,4 +45,18 @@ 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) + } + } + + + } diff --git a/tests/src/test/scala/cats/tests/FunctionTests.scala b/tests/src/test/scala/cats/tests/FunctionTests.scala index 0570cfaa8d3..185dc8eaab0 100644 --- a/tests/src/test/scala/cats/tests/FunctionTests.scala +++ b/tests/src/test/scala/cats/tests/FunctionTests.scala @@ -34,5 +34,13 @@ 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(function1Semigroup[String, 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)) } diff --git a/tests/src/test/scala/cats/tests/ListWrapper.scala b/tests/src/test/scala/cats/tests/ListWrapper.scala index 35f81775704..47ffc8e677d 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,14 @@ object ListWrapper { } } + val monad: Monad[ListWrapper] = monadCombine + + 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/NaturalTransformationTests.scala b/tests/src/test/scala/cats/tests/NaturalTransformationTests.scala index 9669edbb753..4a70916c919 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)) } } } diff --git a/tests/src/test/scala/cats/tests/OptionTTests.scala b/tests/src/test/scala/cats/tests/OptionTTests.scala index a90ec73c1e2..12f50c1ec0c 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, Monoidal} +import cats.{Applicative, Id, Monad, Monoidal, Show} import cats.data.{OptionT, Validated, Xor} import cats.laws.discipline.{ApplicativeTests, FunctorTests, MonadCombineTests, SerializableTests, MonoidalTests, MonadTests} import cats.laws.discipline.arbitrary._ @@ -82,6 +82,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)) @@ -123,11 +129,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))) @@ -140,7 +152,7 @@ class OptionTTests extends CatsSuite { } } - checkAll("OptionT[List, Int]", MonadCombineTests[OptionT[List, ?]].monad[Int, Int, Int]) + checkAll("Monad[OptionT[List, Int]]", MonadTests[OptionT[List, ?]].monad[Int, Int, Int]) checkAll("Monad[OptionT[List, ?]]", SerializableTests.serializable(Monad[OptionT[List, ?]])) { diff --git a/tests/src/test/scala/cats/tests/StreamingTTests.scala b/tests/src/test/scala/cats/tests/StreamingTTests.scala index 5110ccae113..93c722182b8 100644 --- a/tests/src/test/scala/cats/tests/StreamingTTests.scala +++ b/tests/src/test/scala/cats/tests/StreamingTTests.scala @@ -12,7 +12,7 @@ class StreamingTTests extends CatsSuite { { implicit val iso = MonoidalTests.Isomorphisms.invariant[StreamingT[Eval, ?]] - 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, ?]])) @@ -20,7 +20,7 @@ class StreamingTTests extends CatsSuite { { implicit val iso = MonoidalTests.Isomorphisms.invariant[StreamingT[Option, ?]] - 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, ?]])) @@ -28,12 +28,27 @@ class StreamingTTests extends CatsSuite { { implicit val iso = MonoidalTests.Isomorphisms.invariant[StreamingT[List, ?]] - 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, ?]])) } + { + 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 @@ -140,6 +155,44 @@ 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) + } + } + + 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 { diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index 915c2f6bcab..c6921df403d 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -8,7 +8,7 @@ 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 algebra.laws.{OrderLaws, GroupLaws} import scala.util.Try @@ -27,6 +27,10 @@ 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("Validated[String, Int]", GroupLaws[Validated[String, Int]].monoid) + + checkAll("Validated[String, NonEmptyList[Int]]", GroupLaws[Validated[String, NonEmptyList[Int]]].semigroup) + { implicit val S = ListWrapper.partialOrder[String] implicit val I = ListWrapper.partialOrder[Int] @@ -148,4 +152,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) + } + } + } diff --git a/tests/src/test/scala/cats/tests/WriterTTests.scala b/tests/src/test/scala/cats/tests/WriterTTests.scala index 89362ee35d2..d214a08899d 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 @@ -71,7 +89,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]) @@ -90,7 +108,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], ?]] @@ -113,7 +131,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], ?]] @@ -136,7 +154,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], ?]] @@ -163,4 +181,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], ?]])) + } } diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index 924b4db9268..c24f1561ff0 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -159,6 +159,27 @@ 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("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)) diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala index b5ce071a04c..a1334d4409c 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, MonoidalTests} import cats.laws.discipline.eq.tuple3Eq import algebra.laws.{GroupLaws, OrderLaws} @@ -20,6 +20,8 @@ class XorTests extends CatsSuite { checkAll("Xor[String, Int]", MonoidalTests[Xor[String, ?]].monoidal[Int, Int, Int]) checkAll("Monoidal[Xor, ?]", SerializableTests.serializable(Monoidal[Xor[String, ?]])) + checkAll("Xor[String, NonEmptyList[Int]]", GroupLaws[Xor[String, NonEmptyList[Int]]].semigroup) + implicit val eq0 = XorT.xorTEq[Xor[String, ?], String, Int] checkAll("Xor[String, Int]", MonadErrorTests[Xor[String, ?], String].monadError[Int, Int, Int])