diff --git a/AUTHORS.md b/AUTHORS.md index 9dbc1073ff..adf5005c64 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -14,47 +14,66 @@ possible: * Adelbert Chang * Alessandro Lacava + * Alexey Levan * Alissa Pajer * Alistair Johnson * Amir Mohammad Saied * Andrew Jones + * Antoine Comte * Arya Irani + * Ash Pook * Benjamin Thuillier - * Bobby + * Bobby Rauchenberg * Brendan McAdams * Cody Allen * Colt Frederickson * Dale Wijnand * Dave Rostron * David Allsopp + * David Gregory + * Denis Mikhaylov * Derek Wickern * Edmund Noble + * Erik LaBianca * Erik Osheim * Eugene Burmako * Eugene Yokota * Feynman Liang * Frank S. Thomas + * Jean-Rémi Desjardins * Jisoo Park * Josh Marcus + * Julien Richard-Foy * Julien Truffaut * Kenji Yoshida + * Long Cao * Luis Angel Vicente Sanchez + * Luke Wyman * Marc Siegel + * Markus Hauck + * Matthias Lüneberg * Michael Pilquist * Mike Curry * Miles Sabin + * Olli Helenius * Owen Parry * Pascal Voitot + * Paul Phillips * Philip Wills + * Raúl Raja Martínez * Rintcius Blok * Rob Norris * Romain Ruetschi * Ross A. Baker + * Sarunas Valaskevicius + * Shunsuke Otani * Sinisa Louc * Stephen Judkins * Stew O'Connor + * Sumedh Mungee * Travis Brown * Wedens + * Yosef Fertel * Zach Abbott We've tried to include everyone, but if you've made a contribution to diff --git a/build.sbt b/build.sbt index 52babea98e..b0bdff885e 100644 --- a/build.sbt +++ b/build.sbt @@ -36,8 +36,7 @@ lazy val commonSettings = Seq( Resolver.sonatypeRepo("snapshots") ), libraryDependencies ++= Seq( - "com.github.mpilquist" %%% "simulacrum" % "0.5.0", - "org.typelevel" %%% "machinist" % "0.4.1", + "com.github.mpilquist" %%% "simulacrum" % "0.6.1" % "provided", compilerPlugin("org.scalamacros" %% "paradise" % "2.1.0-M5" cross CrossVersion.full), compilerPlugin("org.spire-math" %% "kind-projector" % "0.6.3") ), @@ -45,6 +44,10 @@ lazy val commonSettings = Seq( scalacOptions in (Compile, doc) := (scalacOptions in (Compile, doc)).value.filter(_ != "-Xfatal-warnings") ) ++ warnUnusedImport +lazy val machinistDependencies = Seq( + libraryDependencies += "org.typelevel" %%% "machinist" % "0.4.1" +) + lazy val commonJsSettings = Seq( scalaJSStage in Global := FastOptStage, parallelExecution := false @@ -56,7 +59,9 @@ lazy val commonJvmSettings = Seq( // JVM settings. https://github.com/tkawachi/sbt-doctest/issues/52 ) ++ catsDoctestSettings -lazy val catsSettings = buildSettings ++ commonSettings ++ publishSettings ++ scoverageSettings +lazy val kernelSettings = buildSettings ++ commonSettings ++ publishSettings ++ scoverageSettings + +lazy val catsSettings = kernelSettings ++ machinistDependencies lazy val scalacheckVersion = "1.12.5" @@ -128,15 +133,24 @@ lazy val macros = crossProject.crossType(CrossType.Pure) lazy val macrosJVM = macros.jvm lazy val macrosJS = macros.js - lazy val kernel = crossProject.crossType(CrossType.Pure) .settings(moduleName := "cats-kernel") - .settings(catsSettings:_*) + .settings(kernelSettings:_*) .settings(mimaDefaultSettings:_*) .settings(previousArtifacts := Set( // TODO: Add cats-kernel artifacts as they are released, e.g. // "org.spire-math" %% "cats-kernel" % "0.4.0" )) + .settings(pomPostProcess := { (node) => + import scala.xml._ + import scala.xml.transform._ + def stripIf(f: Node => Boolean) = new RewriteRule { + override def transform(n: Node) = + if (f(n)) NodeSeq.Empty else n + } + val stripProvidedScope = stripIf { n => n.label == "dependency" && (n \ "scope").text == "provided" } + new RuleTransformer(stripProvidedScope).transform(node)(0) + }) .jsSettings(commonJsSettings:_*) .jvmSettings(commonJvmSettings:_*) diff --git a/core/src/main/scala/cats/ApplicativeError.scala b/core/src/main/scala/cats/ApplicativeError.scala new file mode 100644 index 0000000000..f575c7f703 --- /dev/null +++ b/core/src/main/scala/cats/ApplicativeError.scala @@ -0,0 +1,81 @@ +package cats + +import cats.data.{Xor, XorT} + +/** + * An applicative that also allows you to raise and or handle an error value. + * + * This type class allows one to abstract over error-handling applicatives. + */ +trait ApplicativeError[F[_], E] extends Applicative[F] { + /** + * Lift an error into the `F` context. + */ + def raiseError[A](e: E): F[A] + + /** + * Handle any error, potentially recovering from it, by mapping it to an + * `F[A]` value. + * + * @see [[handleError]] to handle any error by simply mapping it to an `A` + * value instead of an `F[A]`. + * + * @see [[recoverWith]] to recover from only certain errors. + */ + def handleErrorWith[A](fa: F[A])(f: E => F[A]): F[A] + + /** + * Handle any error, by mapping it to an `A` value. + * + * @see [[handleErrorWith]] to map to an `F[A]` value instead of simply an + * `A` value. + * + * @see [[recover]] to only recover from certain errors. + */ + def handleError[A](fa: F[A])(f: E => A): F[A] = handleErrorWith(fa)(f andThen pure) + + /** + * Handle errors by turning them into [[cats.data.Xor.Left]] values. + * + * If there is no error, then an [[cats.data.Xor.Right]] value will be returned instead. + * + * All non-fatal errors should be handled by this method. + */ + def attempt[A](fa: F[A]): F[E Xor A] = handleErrorWith( + map(fa)(Xor.right[E, A]) + )(e => pure(Xor.left(e))) + + /** + * Similar to [[attempt]], but wraps the result in a [[cats.data.XorT]] for + * convenience. + */ + def attemptT[A](fa: F[A]): XorT[F, E, A] = XorT(attempt(fa)) + + /** + * Recover from certain errors by mapping them to an `A` value. + * + * @see [[handleError]] to handle any/all errors. + * + * @see [[recoverWith]] to recover from certain errors by mapping them to + * `F[A]` values. + */ + def recover[A](fa: F[A])(pf: PartialFunction[E, A]): F[A] = + handleErrorWith(fa)(e => + (pf andThen pure) applyOrElse(e, raiseError)) + + /** + * Recover from certain errors by mapping them to an `F[A]` value. + * + * @see [[handleErrorWith]] to handle any/all errors. + * + * @see [[recover]] to recover from certain errors by mapping them to `A` + * values. + */ + def recoverWith[A](fa: F[A])(pf: PartialFunction[E, F[A]]): F[A] = + handleErrorWith(fa)(e => + pf applyOrElse(e, raiseError)) +} + +object ApplicativeError { + def apply[F[_], E](implicit F: ApplicativeError[F, E]): ApplicativeError[F, E] = F +} diff --git a/core/src/main/scala/cats/Eval.scala b/core/src/main/scala/cats/Eval.scala index 93ec9297ee..dfe03f7d1e 100644 --- a/core/src/main/scala/cats/Eval.scala +++ b/core/src/main/scala/cats/Eval.scala @@ -223,20 +223,21 @@ object Eval extends EvalInstances { */ sealed abstract class Call[A](val thunk: () => Eval[A]) extends Eval[A] { def memoize: Eval[A] = new Later(() => value) - def value: A = { - def loop(fa: Eval[A]): Eval[A] = fa match { - case call: Eval.Call[A] => - loop(call.thunk()) - case compute: Eval.Compute[A] => - new Eval.Compute[A] { - type Start = compute.Start - val start: () => Eval[Start] = () => compute.start() - val run: Start => Eval[A] = s => loop(compute.run(s)) - } - case other => other - } - - loop(this).value + def value: A = Call.loop(this).value + } + + object Call { + /** Collapse the call stack for eager evaluations */ + private def loop[A](fa: Eval[A]): Eval[A] = fa match { + case call: Eval.Call[A] => + loop(call.thunk()) + case compute: Eval.Compute[A] => + new Eval.Compute[A] { + type Start = compute.Start + val start: () => Eval[Start] = () => compute.start() + val run: Start => Eval[A] = s => loop(compute.run(s)) + } + case other => other } } diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 0db2e66d95..63bc0a0d33 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -24,7 +24,7 @@ import simulacrum.typeclass * * See: [[http://www.cs.nott.ac.uk/~pszgmh/fold.pdf A tutorial on the universality and expressiveness of fold]] */ -@typeclass trait Foldable[F[_]] extends Serializable { self => +@typeclass trait Foldable[F[_]] { self => /** * Left associative fold on 'F' using the function 'f'. @@ -107,6 +107,31 @@ import simulacrum.typeclass G.map2(acc, f(a)) { (_, _) => () } } + /** + * Behaves like traverse_, but uses [[Unapply]] to find the + * [[Applicative]] instance for `G` - used when `G` is a + * type constructor with two or more parameters such as [[cats.data.Xor]] + * + * {{{ + * scala> import cats.data.Xor + * scala> import cats.std.list._ + * scala> def parseInt(s: String): Xor[String, Int] = + * | try { Xor.Right(s.toInt) } + * | catch { case _: NumberFormatException => Xor.Left("boo") } + * scala> val F = Foldable[List] + * scala> F.traverseU_(List("333", "444"))(parseInt) + * res0: Xor[String, Unit] = Right(()) + * scala> F.traverseU_(List("333", "zzz"))(parseInt) + * res1: Xor[String, Unit] = Left(boo) + * }}} + * + * Note that using `traverse_` instead of `traverseU_` would not compile without + * explicitly passing in the type parameters - the type checker has trouble + * inferring the appropriate instance. + */ + def traverseU_[A, GB](fa: F[A])(f: A => GB)(implicit U: Unapply[Applicative, GB]): U.M[Unit] = + traverse_(fa)(f.andThen(U.subst))(U.TC) + /** * Sequence `F[G[A]]` using `Applicative[G]`. * @@ -125,9 +150,31 @@ import simulacrum.typeclass * res1: Option[Unit] = None * }}} */ - def sequence_[G[_]: Applicative, A, B](fga: F[G[A]]): G[Unit] = + def sequence_[G[_]: Applicative, A](fga: F[G[A]]): G[Unit] = traverse_(fga)(identity) + /** + * Behaves like sequence_, but uses [[Unapply]] to find the + * [[Applicative]] instance for `G` - used when `G` is a + * type constructor with two or more parameters such as [[cats.data.Xor]] + * + * {{{ + * scala> import cats.data.Xor + * scala> import cats.std.list._ + * scala> val F = Foldable[List] + * scala> F.sequenceU_(List(Xor.right[String, Int](333), Xor.Right(444))) + * res0: Xor[String, Unit] = Right(()) + * scala> F.sequenceU_(List(Xor.right[String, Int](333), Xor.Left("boo"))) + * res1: Xor[String, Unit] = Left(boo) + * }}} + * + * Note that using `sequence_` instead of `sequenceU_` would not compile without + * explicitly passing in the type parameters - the type checker has trouble + * inferring the appropriate instance. + */ + def sequenceU_[GA](fa: F[GA])(implicit U: Unapply[Applicative, GA]): U.M[Unit] = + traverseU_(fa)(identity) + /** * Fold implemented using the given `MonoidK[G]` instance. * diff --git a/core/src/main/scala/cats/MonadError.scala b/core/src/main/scala/cats/MonadError.scala index 4541a44751..0284edcab5 100644 --- a/core/src/main/scala/cats/MonadError.scala +++ b/core/src/main/scala/cats/MonadError.scala @@ -7,74 +7,7 @@ import cats.data.{Xor, XorT} * * This type class allows one to abstract over error-handling monads. */ -trait MonadError[F[_], E] extends Monad[F] { - /** - * Lift an error into the `F` context. - */ - def raiseError[A](e: E): F[A] - - /** - * Handle any error, potentially recovering from it, by mapping it to an - * `F[A]` value. - * - * @see [[handleError]] to handle any error by simply mapping it to an `A` - * value instead of an `F[A]`. - * - * @see [[recoverWith]] to recover from only certain errors. - */ - def handleErrorWith[A](fa: F[A])(f: E => F[A]): F[A] - - /** - * Handle any error, by mapping it to an `A` value. - * - * @see [[handleErrorWith]] to map to an `F[A]` value instead of simply an - * `A` value. - * - * @see [[recover]] to only recover from certain errors. - */ - def handleError[A](fa: F[A])(f: E => A): F[A] = handleErrorWith(fa)(f andThen pure) - - /** - * Handle errors by turning them into [[cats.data.Xor.Left]] values. - * - * If there is no error, then an [[cats.data.Xor.Right]] value will be returned instead. - * - * All non-fatal errors should be handled by this method. - */ - def attempt[A](fa: F[A]): F[E Xor A] = handleErrorWith( - map(fa)(Xor.right[E, A]) - )(e => pure(Xor.left(e))) - - /** - * Similar to [[attempt]], but wraps the result in a [[cats.data.XorT]] for - * convenience. - */ - def attemptT[A](fa: F[A]): XorT[F, E, A] = XorT(attempt(fa)) - - /** - * Recover from certain errors by mapping them to an `A` value. - * - * @see [[handleError]] to handle any/all errors. - * - * @see [[recoverWith]] to recover from certain errors by mapping them to - * `F[A]` values. - */ - def recover[A](fa: F[A])(pf: PartialFunction[E, A]): F[A] = - handleErrorWith(fa)(e => - (pf andThen pure) applyOrElse(e, raiseError)) - - /** - * Recover from certain errors by mapping them to an `F[A]` value. - * - * @see [[handleErrorWith]] to handle any/all errors. - * - * @see [[recover]] to recover from certain errors by mapping them to `A` - * values. - */ - def recoverWith[A](fa: F[A])(pf: PartialFunction[E, F[A]]): F[A] = - handleErrorWith(fa)(e => - pf applyOrElse(e, raiseError)) -} +trait MonadError[F[_], E] extends ApplicativeError[F, E] with Monad[F] object MonadError { def apply[F[_], E](implicit F: MonadError[F, E]): MonadError[F, E] = F diff --git a/core/src/main/scala/cats/SemigroupK.scala b/core/src/main/scala/cats/SemigroupK.scala index adfdb3504b..9313ffbaf5 100644 --- a/core/src/main/scala/cats/SemigroupK.scala +++ b/core/src/main/scala/cats/SemigroupK.scala @@ -20,7 +20,7 @@ import simulacrum.{op, typeclass} * The combination operation just depends on the structure of F, * but not the structure of A. */ -@typeclass trait SemigroupK[F[_]] extends Serializable { self => +@typeclass trait SemigroupK[F[_]] { self => /** * Combine two F[A] values. diff --git a/core/src/main/scala/cats/Show.scala b/core/src/main/scala/cats/Show.scala index aa1fd19f3a..214ad82ceb 100644 --- a/core/src/main/scala/cats/Show.scala +++ b/core/src/main/scala/cats/Show.scala @@ -10,7 +10,7 @@ import cats.functor.Contravariant * made a toString method, a Show instance will only exist if someone * explicitly provided one. */ -@typeclass trait Show[T] extends Serializable { +@typeclass trait Show[T] { def show(f: T): String } diff --git a/core/src/main/scala/cats/arrow/Arrow.scala b/core/src/main/scala/cats/arrow/Arrow.scala index bbf1b15c52..81820db3fd 100644 --- a/core/src/main/scala/cats/arrow/Arrow.scala +++ b/core/src/main/scala/cats/arrow/Arrow.scala @@ -5,11 +5,58 @@ import cats.functor.Strong trait Arrow[F[_, _]] extends Split[F] with Strong[F] with Category[F] { self => + /** + * Lift a function into the context of an Arrow + */ def lift[A, B](f: A => B): F[A, B] + /** + * Create a new arrow from an existing arrow that applies `f` to the input + * of the original arrow and then applies `g` to the output. + * + * Example: + * {{{ + * scala> import cats.std.function._ + * scala> import cats.arrow.Arrow + * scala> val fab: Double => Double = x => x + 0.3 + * scala> val f: Int => Double = x => x.toDouble / 2 + * scala> val g: Double => Double = x => x * 3 + * scala> val dimapArrow = Arrow[Function1].dimap(fab)(f)(g) + * scala> dimapArrow(3) + * res0: Double = 5.4 + * }}} + */ def dimap[A, B, C, D](fab: F[A, B])(f: C => A)(g: B => D): F[C, D] = compose(lift(g), andThen(lift(f), fab)) + /** + * Create a new arrow that takes two inputs, but only modifies the first input + * + * Example: + * {{{ + * scala> import cats.std.function._ + * scala> import cats.arrow.Arrow + * scala> val f: Int => Int = _ * 2 + * scala> val fab = Arrow[Function1].first[Int,Int,Int](f) + * scala> fab((2,3)) + * res0: (Int, Int) = (4,3) + * }}} + */ + def first[A, B, C](fa: F[A, B]): F[(A, C), (B, C)] + + /** + * Create a new arrow that takes two inputs, but only modifies the second input + * + * Example: + * {{{ + * scala> import cats.std.function._ + * scala> import cats.arrow.Arrow + * scala> val f: Int => Int = _ * 2 + * scala> val fab = Arrow[Function1].second[Int,Int,Int](f) + * scala> fab((2,3)) + * res0: (Int, Int) = (2,6) + * }}} + */ def second[A, B, C](fa: F[A, B]): F[(C, A), (C, B)] = { def swap[X, Y]: F[(X, Y), (Y, X)] = lift[(X, Y), (Y, X)] { case (x, y) => (y, x) } compose(swap, compose(first[A, B, C](fa), swap)) @@ -22,6 +69,7 @@ trait Arrow[F[_, _]] extends Split[F] with Strong[F] with Category[F] { self => * Example: * {{{ * scala> import cats.std.function._ + * scala> import cats.arrow.Arrow * scala> val toLong: Int => Long = _.toLong * scala> val toDouble: Float => Double = _.toDouble * scala> val f: ((Int, Float)) => (Long, Double) = Arrow[Function1].split(toLong, toDouble) diff --git a/core/src/main/scala/cats/data/StreamingT.scala b/core/src/main/scala/cats/data/StreamingT.scala index 3c104fd194..214a0c8021 100644 --- a/core/src/main/scala/cats/data/StreamingT.scala +++ b/core/src/main/scala/cats/data/StreamingT.scala @@ -70,7 +70,9 @@ sealed abstract class StreamingT[F[_], A] extends Product with Serializable { lh */ def filter(f: A => Boolean)(implicit ev: Functor[F]): StreamingT[F, A] = this match { - case Cons(a, ft) => if (f(a)) this else Wait(ft.map(_.filter(f))) + case Cons(a, ft) => + val tail = ft.map(_.filter(f)) + if (f(a)) Cons(a, tail) else Wait(tail) case Wait(ft) => Wait(ft.map(_.filter(f))) case Empty() => this } diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index 1a5c06160f..c1259ec3c6 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -234,8 +234,8 @@ private[data] sealed abstract class ValidatedInstances extends ValidatedInstance override def leftMap[A, B, C](fab: Validated[A, B])(f: A => C): Validated[C, B] = fab.leftMap(f) } - implicit def validatedInstances[E](implicit E: Semigroup[E]): Traverse[Validated[E, ?]] with Applicative[Validated[E, ?]] = - new Traverse[Validated[E, ?]] with Applicative[Validated[E,?]] { + implicit def validatedInstances[E](implicit E: Semigroup[E]): Traverse[Validated[E, ?]] with ApplicativeError[Validated[E, ?], E] = + new Traverse[Validated[E, ?]] with ApplicativeError[Validated[E, ?], E] { def traverse[F[_]: Applicative, A, B](fa: Validated[E,A])(f: A => F[B]): F[Validated[E,B]] = fa.traverse(f) @@ -256,6 +256,13 @@ private[data] sealed abstract class ValidatedInstances extends ValidatedInstance def product[A, B](fa: Validated[E, A], fb: Validated[E, B]): Validated[E, (A, B)] = fa.product(fb)(E) + + def handleErrorWith[A](fa: Validated[E, A])(f: E => Validated[E, A]): Validated[E, A] = + fa match { + case Validated.Invalid(e) => f(e) + case v @ Validated.Valid(_) => v + } + def raiseError[A](e: E): Validated[E, A] = Validated.Invalid(e) } } diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index b50282cfe3..7d27cbb379 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -40,13 +40,13 @@ final case class XorT[F[_], A, B](value: F[A Xor B]) { XorT(F.map(value)(_.recover(pf))) def recoverWith(pf: PartialFunction[A, XorT[F, A, B]])(implicit F: Monad[F]): XorT[F, A, B] = - XorT(F.flatMap(value) { xor => - xor match { - case Xor.Left(a) if pf.isDefinedAt(a) => pf(a).value - case _ => F.pure(xor) - } + XorT(F.flatMap(value) { + case Xor.Left(a) if pf.isDefinedAt(a) => pf(a).value + case other => F.pure(other) }) + def valueOr[BB >: B](f: A => BB)(implicit F: Functor[F]): F[BB] = fold(f, identity) + def forall(f: B => Boolean)(implicit F: Functor[F]): F[Boolean] = F.map(value)(_.forall(f)) def exists(f: B => Boolean)(implicit F: Functor[F]): F[Boolean] = F.map(value)(_.exists(f)) diff --git a/core/src/main/scala/cats/free/FreeApplicative.scala b/core/src/main/scala/cats/free/FreeApplicative.scala index 204815b0bb..92d68eef6a 100644 --- a/core/src/main/scala/cats/free/FreeApplicative.scala +++ b/core/src/main/scala/cats/free/FreeApplicative.scala @@ -30,7 +30,7 @@ sealed abstract class FreeApplicative[F[_], A] extends Product with Serializable final def foldMap[G[_]](f: F ~> G)(implicit G: Applicative[G]): G[A] = this match { case Pure(a) => G.pure(a) - case Ap(pivot, fn) => G.ap(f(pivot))(fn.foldMap(f)) + case Ap(pivot, fn) => G.map2(f(pivot), fn.foldMap(f))((a, g) => g(a)) } /** Interpret/run the operations using the semantics of `Applicative[F]`. diff --git a/core/src/main/scala/cats/functor/Invariant.scala b/core/src/main/scala/cats/functor/Invariant.scala index b8a7788c57..ac2653f7b5 100644 --- a/core/src/main/scala/cats/functor/Invariant.scala +++ b/core/src/main/scala/cats/functor/Invariant.scala @@ -6,7 +6,7 @@ import simulacrum.typeclass /** * Must obey the laws defined in cats.laws.InvariantLaws. */ -@typeclass trait Invariant[F[_]] extends Any with Serializable { self => +@typeclass trait Invariant[F[_]] { self => def imap[A, B](fa: F[A])(f: A => B)(g: B => A): F[B] /** diff --git a/core/src/main/scala/cats/state/StateT.scala b/core/src/main/scala/cats/state/StateT.scala index 6f6ccce926..5a41f7ea7f 100644 --- a/core/src/main/scala/cats/state/StateT.scala +++ b/core/src/main/scala/cats/state/StateT.scala @@ -1,9 +1,7 @@ package cats package state -import cats.free.Trampoline import cats.data.Kleisli -import cats.std.function.function0Instance /** * `StateT[F, S, A]` is similar to `Kleisli[F, S, A]` in that it takes an `S` @@ -125,7 +123,7 @@ object StateT extends StateTInstances { StateT(s => F.pure((s, a))) } -private[state] sealed abstract class StateTInstances extends StateTInstances0 { +private[state] sealed abstract class StateTInstances { implicit def stateTMonadState[F[_], S](implicit F: Monad[F]): MonadState[StateT[F, S, ?], S] = new MonadState[StateT[F, S, ?], S] { def pure[A](a: A): StateT[F, S, A] = @@ -143,17 +141,12 @@ private[state] sealed abstract class StateTInstances extends StateTInstances0 { } } -private[state] sealed abstract class StateTInstances0 { - implicit def stateMonadState[S]: MonadState[State[S, ?], S] = - StateT.stateTMonadState[Trampoline, S] -} - // To workaround SI-7139 `object State` needs to be defined inside the package object // together with the type alias. private[state] abstract class StateFunctions { def apply[S, A](f: S => (S, A)): State[S, A] = - StateT.applyF(Trampoline.done((s: S) => Trampoline.done(f(s)))) + StateT.applyF(Now((s: S) => Now(f(s)))) /** * Return `a` and maintain the input state. diff --git a/core/src/main/scala/cats/state/package.scala b/core/src/main/scala/cats/state/package.scala index bba80c5f21..47eaf1ca70 100644 --- a/core/src/main/scala/cats/state/package.scala +++ b/core/src/main/scala/cats/state/package.scala @@ -1,8 +1,6 @@ package cats -import free.Trampoline - package object state { - type State[S, A] = StateT[Trampoline, S, A] + type State[S, A] = StateT[Eval, S, A] object State extends StateFunctions } diff --git a/core/src/main/scala/cats/std/vector.scala b/core/src/main/scala/cats/std/vector.scala index 88d9797ceb..bb32ea8988 100644 --- a/core/src/main/scala/cats/std/vector.scala +++ b/core/src/main/scala/cats/std/vector.scala @@ -4,9 +4,13 @@ package std import cats.data.Streaming import cats.syntax.show._ +import scala.annotation.tailrec +import scala.collection.+: +import scala.collection.immutable.VectorBuilder + trait VectorInstances { - implicit val vectorInstance: Traverse[Vector] with MonadCombine[Vector] = - new Traverse[Vector] with MonadCombine[Vector] { + implicit val vectorInstance: Traverse[Vector] with MonadCombine[Vector] with CoflatMap[Vector] = + new Traverse[Vector] with MonadCombine[Vector] with CoflatMap[Vector] { def empty[A]: Vector[A] = Vector.empty[A] @@ -23,6 +27,15 @@ trait VectorInstances { override def map2[A, B, Z](fa: Vector[A], fb: Vector[B])(f: (A, B) => Z): Vector[Z] = fa.flatMap(a => fb.map(b => f(a, b))) + def coflatMap[A, B](fa: Vector[A])(f: Vector[A] => B): Vector[B] = { + @tailrec def loop(builder: VectorBuilder[B], as: Vector[A]): Vector[B] = + as match { + case _ +: rest => loop(builder += f(as), rest) + case _ => builder.result() + } + loop(new VectorBuilder[B], fa) + } + def foldLeft[A, B](fa: Vector[A], b: B)(f: (B, A) => B): B = fa.foldLeft(b)(f) diff --git a/core/src/main/scala/cats/syntax/coproduct.scala b/core/src/main/scala/cats/syntax/coproduct.scala index 60043f9331..b4444dfd72 100644 --- a/core/src/main/scala/cats/syntax/coproduct.scala +++ b/core/src/main/scala/cats/syntax/coproduct.scala @@ -7,7 +7,37 @@ trait CoproductSyntax { implicit def coproductSyntax[F[_], A](a: F[A]): CoproductOps[F, A] = new CoproductOps(a) } -final class CoproductOps[F[_], A](val a: F[A]) extends AnyVal { - def leftc[G[_]]: Coproduct[F, G, A] = Coproduct.leftc(a) - def rightc[G[_]]: Coproduct[G, F, A] = Coproduct.rightc(a) +final class CoproductOps[F[_], A](val fa: F[A]) extends AnyVal { + + /** + * Lift an `F[A]` into a `Coproduct[F, G, A]` for any type constructor `G[_]`. + * + * @see [[rightc]] to swap the order of `F` and `G` in the result type. + * + * Example: + * {{{ + * scala> import cats.data.Coproduct + * scala> import cats.Eval + * scala> import cats.syntax.coproduct._ + * scala> List(1, 2, 3).leftc[Eval] + * res0: Coproduct[List, Eval, Int] = Coproduct(Left(List(1, 2, 3))) + * }}} + */ + def leftc[G[_]]: Coproduct[F, G, A] = Coproduct.leftc(fa) + + /** + * Lift an `F[A]` into a `Coproduct[G, F, A]` for any type constructor `G[_]`. + * + * @see [[leftc]] to swap the order of `F` and `G` in the result type. + * + * Example: + * {{{ + * scala> import cats.data.Coproduct + * scala> import cats.Eval + * scala> import cats.syntax.coproduct._ + * scala> List(1, 2, 3).rightc[Eval] + * res0: Coproduct[Eval, List, Int] = Coproduct(Right(List(1, 2, 3))) + * }}} + */ + def rightc[G[_]]: Coproduct[G, F, A] = Coproduct.rightc(fa) } diff --git a/core/src/main/scala/cats/syntax/either.scala b/core/src/main/scala/cats/syntax/either.scala index a748020604..36c9c102d7 100644 --- a/core/src/main/scala/cats/syntax/either.scala +++ b/core/src/main/scala/cats/syntax/either.scala @@ -8,5 +8,23 @@ trait EitherSyntax { } final class EitherOps[A, B](val eab: Either[A, B]) extends AnyVal { + + /** + * Convert a `scala.util.Either` into a [[cats.data.Xor]]. + * + * Example: + * {{{ + * scala> import cats.data.Xor + * scala> import cats.syntax.either._ + * + * scala> val i: Either[String, Int] = Right(3) + * scala> i.toXor + * res0: Xor[String, Int] = Right(3) + * + * scala> val s: Either[String, Int] = Left("error!") + * scala> s.toXor + * res0: Xor[String, Int] = Left(error!) + * }}} + */ def toXor: A Xor B = Xor.fromEither(eab) } diff --git a/core/src/main/scala/cats/syntax/foldable.scala b/core/src/main/scala/cats/syntax/foldable.scala index 7e165babae..d71a7628e7 100644 --- a/core/src/main/scala/cats/syntax/foldable.scala +++ b/core/src/main/scala/cats/syntax/foldable.scala @@ -16,5 +16,20 @@ trait FoldableSyntax extends Foldable.ToFoldableOps with FoldableSyntax1 { final class NestedFoldableOps[F[_], G[_], A](fga: F[G[A]])(implicit F: Foldable[F]) { def sequence_(implicit G: Applicative[G]): G[Unit] = F.sequence_(fga) + + /** + * @see [[Foldable.foldK]]. + * + * Example: + * {{{ + * scala> import cats.std.list._ + * scala> import cats.std.set._ + * scala> import cats.syntax.foldable._ + * + * scala> val l: List[Set[Int]] = List(Set(1, 2), Set(2, 3), Set(3, 4)) + * scala> l.foldK + * res0: Set[Int] = Set(1, 2, 3, 4) + * }}} + */ def foldK(implicit G: MonoidK[G]): G[A] = F.foldK(fga) } diff --git a/core/src/main/scala/cats/syntax/monadCombine.scala b/core/src/main/scala/cats/syntax/monadCombine.scala index 9d912f7c19..8490b0de5b 100644 --- a/core/src/main/scala/cats/syntax/monadCombine.scala +++ b/core/src/main/scala/cats/syntax/monadCombine.scala @@ -8,5 +8,19 @@ trait MonadCombineSyntax { } final class NestedMonadCombineOps[F[_], G[_], A](fga: F[G[A]])(implicit F: MonadCombine[F]) { + + /** + * @see [[MonadCombine.unite]] + * + * Example: + * {{{ + * scala> import cats.data.Streaming + * scala> import cats.std.list._ + * scala> import cats.syntax.monadCombine._ + * scala> val x: List[Streaming[Int]] = List(Streaming(1, 2), Streaming(3, 4)) + * scala> x.unite + * res0: List[Int] = List(1, 2, 3, 4) + * }}} + */ def unite(implicit G: Foldable[G]): F[A] = F.unite(fga) } diff --git a/core/src/main/scala/cats/syntax/package.scala b/core/src/main/scala/cats/syntax/package.scala index c61f8df9fe..fb1cc368e4 100644 --- a/core/src/main/scala/cats/syntax/package.scala +++ b/core/src/main/scala/cats/syntax/package.scala @@ -6,6 +6,7 @@ package object syntax { object monoidal extends MonoidalSyntax object bifunctor extends BifunctorSyntax object coflatMap extends CoflatMapSyntax + object coproduct extends CoproductSyntax object comonad extends ComonadSyntax object compose extends ComposeSyntax object contravariant extends ContravariantSyntax diff --git a/docs/src/main/tut/eq.md b/docs/src/main/tut/eq.md new file mode 100644 index 0000000000..61a580cb54 --- /dev/null +++ b/docs/src/main/tut/eq.md @@ -0,0 +1,9 @@ +--- +layout: default +title: "Eq" +section: "typeclasses" +source: "https://github.com/non/cats/blob/master/kernel/src/main/scala/cats/Eq.scala" +scaladoc: "#cats.Eq" +--- +# Eq + diff --git a/docs/src/main/tut/group.md b/docs/src/main/tut/group.md new file mode 100644 index 0000000000..043e27b3bb --- /dev/null +++ b/docs/src/main/tut/group.md @@ -0,0 +1,9 @@ +--- +layout: default +title: "Group" +section: "typeclasses" +source: "https://github.com/non/cats/blob/master/kernel/src/main/scala/cats/Group.scala" +scaladoc: "#cats.Group" +--- +# Group + diff --git a/docs/src/main/tut/monoid.md b/docs/src/main/tut/monoid.md index e9f9b6020f..b1b6fe1c9e 100644 --- a/docs/src/main/tut/monoid.md +++ b/docs/src/main/tut/monoid.md @@ -2,7 +2,7 @@ layout: default title: "Monoid" section: "typeclasses" -source: "https://github.com/non/algebra/blob/master/core/src/main/scala/algebra/Monoid.scala" +source: "https://github.com/non/cats/blob/master/kernel/src/main/scala/cats/Monoid.scala" --- # Monoid @@ -81,13 +81,3 @@ Thus. ```tut l.foldMap(i => (i, i.toString)) // do both of the above in one pass, hurrah! ``` - -------------------------------------------------------------------------------- - -N.B. -Cats does not define a `Monoid` type class itself, it uses the [`Monoid` -trait](https://github.com/non/algebra/blob/master/core/src/main/scala/algebra/Monoid.scala) -which is defined in the [algebra project](https://github.com/non/algebra) on -which it depends. The [`cats` package object](https://github.com/non/cats/blob/master/core/src/main/scala/cats/package.scala) -defines type aliases to the `Monoid` from algebra, so that you can -`import cats.Monoid`. diff --git a/docs/src/main/tut/order.md b/docs/src/main/tut/order.md new file mode 100644 index 0000000000..f79a9d295f --- /dev/null +++ b/docs/src/main/tut/order.md @@ -0,0 +1,9 @@ +--- +layout: default +title: "Order" +section: "typeclasses" +source: "https://github.com/non/cats/blob/master/kernel/src/main/scala/cats/Order.scala" +scaladoc: "#cats.Order" +--- +# Order + diff --git a/docs/src/main/tut/partialorder.md b/docs/src/main/tut/partialorder.md new file mode 100644 index 0000000000..8e0c9894cd --- /dev/null +++ b/docs/src/main/tut/partialorder.md @@ -0,0 +1,9 @@ +--- +layout: default +title: "PartialOrder" +section: "typeclasses" +source: "https://github.com/non/cats/blob/master/kernel/src/main/scala/cats/PartialOrder.scala" +scaladoc: "#cats.PartialOrder" +--- +# PartialOrder + diff --git a/docs/src/main/tut/semigroup.md b/docs/src/main/tut/semigroup.md index 3dad3715cd..75d9bbd1ba 100644 --- a/docs/src/main/tut/semigroup.md +++ b/docs/src/main/tut/semigroup.md @@ -2,7 +2,7 @@ layout: default title: "Semigroup" section: "typeclasses" -source: "https://github.com/non/algebra/blob/master/core/src/main/scala/algebra/Semigroup.scala" +source: "https://github.com/non/cats/blob/master/kernel/src/main/scala/cats/Semigroup.scala" --- # Semigroup @@ -94,11 +94,3 @@ Option. If we try to use Some and None, we'll get errors: Some(1) |+| None None |+| Some(1) ``` - -N.B. -Cats does not define a `Semigroup` type class itself, it uses the [`Semigroup` -trait](https://github.com/non/algebra/blob/master/core/src/main/scala/algebra/Semigroup.scala) -which is defined in the [algebra project](https://github.com/non/algebra) on -which it depends. The [`cats` package object](https://github.com/non/cats/blob/master/core/src/main/scala/cats/package.scala) -defines type aliases to the `Semigroup` from algebra, so that you can -`import cats.Semigroup`. \ No newline at end of file diff --git a/docs/src/main/tut/state.md b/docs/src/main/tut/state.md index d7be7078bd..82616be992 100644 --- a/docs/src/main/tut/state.md +++ b/docs/src/main/tut/state.md @@ -132,7 +132,6 @@ Let's write a new version of `nextLong` using `State`: ```tut:silent import cats.state.State -import cats.std.function._ val nextLong: State[Seed, Long] = State(seed => (seed.next, seed.long)) @@ -159,16 +158,16 @@ val createRobot: State[Seed, Robot] = } yield Robot(id, sentient, name, model) ``` -At this point, we have not yet created a robot; we have written instructions for creating a robot. We need to pass in an initial seed value, and then we can call `run` to actually create the robot: +At this point, we have not yet created a robot; we have written instructions for creating a robot. We need to pass in an initial seed value, and then we can call `value` to actually create the robot: ```tut -val (finalState, robot) = createRobot.run(initialSeed).run +val (finalState, robot) = createRobot.run(initialSeed).value ``` If we only care about the robot and not the final state, then we can use `runA`: ```tut -val robot = createRobot.runA(initialSeed).run +val robot = createRobot.runA(initialSeed).value ``` The `createRobot` implementation reads much like the imperative code we initially wrote for the mutable RNG. However, this implementation is free of mutation and side-effects. Since this code is referentially transparent, we can perform the refactoring that we tried earlier without affecting the result: @@ -189,11 +188,11 @@ val createRobot: State[Seed, Robot] = { ``` ```tut -val robot = createRobot.runA(initialSeed).run +val robot = createRobot.runA(initialSeed).value ``` This may seem surprising, but keep in mind that `b` isn't simply a `Boolean`. It is a function that takes a seed and _returns_ a `Boolean`, threading state along the way. Since the seed that is being passed into `b` changes from line to line, so do the returned `Boolean` values. ## Fine print -TODO explain StateT and the fact that State is an alias for StateT with trampolining. +TODO explain StateT and the fact that State is an alias for StateT with Eval. diff --git a/kernel/src/main/scala/cats/Eq.scala b/kernel/src/main/scala/cats/Eq.scala index 2d4b034f5f..467bd899cd 100644 --- a/kernel/src/main/scala/cats/Eq.scala +++ b/kernel/src/main/scala/cats/Eq.scala @@ -1,5 +1,7 @@ package cats +import simulacrum.typeclass + import scala.{specialized => sp} import scala.math.Equiv @@ -9,7 +11,7 @@ import scala.math.Equiv * type. Any 2 instances `x` and `y` are equal if `eqv(x, y)` is `true`. * Moreover, `eqv` should form an equivalence relation. */ -trait Eq[@sp A] extends Any with Serializable { self => +@typeclass trait Eq[@sp A] { self => /** * Returns `true` if `x` and `y` are equivalent, `false` otherwise. @@ -58,11 +60,6 @@ trait EqFunctions { object Eq extends EqFunctions { - /** - * Access an implicit `Eq[A]`. - */ - @inline final def apply[A](implicit ev: Eq[A]): Eq[A] = ev - /** * Convert an implicit `Eq[B]` to an `Eq[A]` using the given * function `f`. diff --git a/kernel/src/main/scala/cats/Group.scala b/kernel/src/main/scala/cats/Group.scala index b5db4eed7f..ae38e239cd 100644 --- a/kernel/src/main/scala/cats/Group.scala +++ b/kernel/src/main/scala/cats/Group.scala @@ -1,11 +1,12 @@ package cats +import simulacrum.typeclass import scala.{ specialized => sp } /** * A group is a monoid where each element has an inverse. */ -trait Group[@sp(Int, Long, Float, Double) A] extends Any with Monoid[A] { +@typeclass trait Group[@sp(Int, Long, Float, Double) A] extends Monoid[A] { /** * Find the inverse of `a`. @@ -39,10 +40,4 @@ trait GroupFunctions[G[T] <: Group[T]] extends MonoidFunctions[Group] { ev.remove(x, y) } -object Group extends GroupFunctions[Group] { - - /** - * Access an implicit `Group[A]`. - */ - @inline final def apply[A](implicit ev: Group[A]): Group[A] = ev -} +object Group extends GroupFunctions[Group] diff --git a/kernel/src/main/scala/cats/Monoid.scala b/kernel/src/main/scala/cats/Monoid.scala index 32f9f334b7..5f1509c17d 100644 --- a/kernel/src/main/scala/cats/Monoid.scala +++ b/kernel/src/main/scala/cats/Monoid.scala @@ -1,5 +1,6 @@ package cats +import simulacrum.typeclass import scala.{ specialized => sp } /** @@ -8,7 +9,7 @@ import scala.{ specialized => sp } * `combine(x, empty) == combine(empty, x) == x`. For example, if we have `Monoid[String]`, * with `combine` as string concatenation, then `empty = ""`. */ -trait Monoid[@sp(Int, Long, Float, Double) A] extends Any with Semigroup[A] { +@typeclass trait Monoid[@sp(Int, Long, Float, Double) A] extends Semigroup[A] { /** * Return the identity element for this monoid. @@ -43,10 +44,4 @@ trait MonoidFunctions[M[T] <: Monoid[T]] extends SemigroupFunctions[M] { ev.combineAll(as) } -object Monoid extends MonoidFunctions[Monoid] { - - /** - * Access an implicit `Monoid[A]`. - */ - @inline final def apply[A](implicit ev: Monoid[A]): Monoid[A] = ev -} +object Monoid extends MonoidFunctions[Monoid] diff --git a/kernel/src/main/scala/cats/Order.scala b/kernel/src/main/scala/cats/Order.scala index 8af47bfb3e..949c1e7c3d 100644 --- a/kernel/src/main/scala/cats/Order.scala +++ b/kernel/src/main/scala/cats/Order.scala @@ -1,5 +1,6 @@ package cats +import simulacrum.typeclass import scala.{specialized => sp} /** @@ -19,7 +20,7 @@ import scala.{specialized => sp} * * By the totality law, x <= y and y <= x cannot be both false. */ -trait Order[@sp A] extends Any with PartialOrder[A] { self => +@typeclass trait Order[@sp A] extends PartialOrder[A] { self => /** * Result of comparing `x` with `y`. Returns an Int whose sign is: @@ -29,7 +30,7 @@ trait Order[@sp A] extends Any with PartialOrder[A] { self => */ def compare(x: A, y: A): Int - def partialCompare(x: A, y: A): Double = compare(x, y).toDouble + override def partialCompare(x: A, y: A): Double = compare(x, y).toDouble /** * If x <= y, return x, else return y. @@ -147,11 +148,6 @@ trait OrderFunctions { object Order extends OrderFunctions { - /** - * Access an implicit `Eq[A]`. - */ - @inline final def apply[A](implicit ev: Order[A]) = ev - /** * Convert an implicit `Order[A]` to an `Order[B]` using the given * function `f`. diff --git a/kernel/src/main/scala/cats/PartialOrder.scala b/kernel/src/main/scala/cats/PartialOrder.scala index c35a1f9866..a64eab6158 100644 --- a/kernel/src/main/scala/cats/PartialOrder.scala +++ b/kernel/src/main/scala/cats/PartialOrder.scala @@ -1,5 +1,6 @@ package cats +import simulacrum.typeclass import scala.{specialized => sp} /** @@ -21,7 +22,7 @@ import scala.{specialized => sp} * true false = -1.0 (corresponds to x < y) * false true = 1.0 (corresponds to x > y) */ -trait PartialOrder[@sp A] extends Any with Eq[A] { self => +@typeclass trait PartialOrder[@sp A] extends Eq[A] { self => /** * Result of comparing `x` with `y`. Returns NaN if operands are not @@ -88,7 +89,7 @@ trait PartialOrder[@sp A] extends Any with Eq[A] { self => /** * Returns true if `x` = `y`, false otherwise. */ - def eqv(x: A, y: A): Boolean = partialCompare(x, y) == 0 + override def eqv(x: A, y: A): Boolean = partialCompare(x, y) == 0 /** * Returns true if `x` <= `y`, false otherwise. @@ -137,11 +138,6 @@ trait PartialOrderFunctions { object PartialOrder extends PartialOrderFunctions { - /** - * Access an implicit `Eq[A]`. - */ - @inline final def apply[A](implicit ev: PartialOrder[A]) = ev - /** * Convert an implicit `PartialOrder[A]` to an `PartialOrder[B]` * using the given function `f`. diff --git a/kernel/src/main/scala/cats/Semigroup.scala b/kernel/src/main/scala/cats/Semigroup.scala index f59e90968a..ce0bed46a9 100644 --- a/kernel/src/main/scala/cats/Semigroup.scala +++ b/kernel/src/main/scala/cats/Semigroup.scala @@ -1,12 +1,13 @@ package cats +import simulacrum.typeclass import scala.{ specialized => sp } import scala.annotation.{ switch, tailrec } /** * A semigroup is any set `A` with an associative operation (`combine`). */ -trait Semigroup[@sp(Int, Long, Float, Double) A] extends Any with Serializable { +@typeclass trait Semigroup[@sp(Int, Long, Float, Double) A] { /** * Associative operation taking which combines two values. @@ -64,10 +65,4 @@ trait SemigroupFunctions[S[T] <: Semigroup[T]] { ev.combineAllOption(as) } -object Semigroup extends SemigroupFunctions[Semigroup] { - - /** - * Access an implicit `Semigroup[A]`. - */ - @inline final def apply[A](implicit ev: Semigroup[A]) = ev -} +object Semigroup extends SemigroupFunctions[Semigroup] diff --git a/laws/src/main/scala/cats/laws/ApplicativeErrorLaws.scala b/laws/src/main/scala/cats/laws/ApplicativeErrorLaws.scala new file mode 100644 index 0000000000..b912534bf7 --- /dev/null +++ b/laws/src/main/scala/cats/laws/ApplicativeErrorLaws.scala @@ -0,0 +1,44 @@ +package cats +package laws + +import cats.data.{Xor, XorT} + +// Taken from http://functorial.com/psc-pages/docs/Control/Monad/Error/Class/index.html +trait ApplicativeErrorLaws[F[_], E] extends ApplicativeLaws[F] { + implicit override def F: ApplicativeError[F, E] + + def applicativeErrorHandleWith[A](e: E, f: E => F[A]): IsEq[F[A]] = + F.handleErrorWith(F.raiseError[A](e))(f) <-> f(e) + + def applicativeErrorHandle[A](e: E, f: E => A): IsEq[F[A]] = + F.handleError(F.raiseError[A](e))(f) <-> F.pure(f(e)) + + def handleErrorWithPure[A](a: A, f: E => F[A]): IsEq[F[A]] = + F.handleErrorWith(F.pure(a))(f) <-> F.pure(a) + + def handleErrorPure[A](a: A, f: E => A): IsEq[F[A]] = + F.handleError(F.pure(a))(f) <-> F.pure(a) + + def raiseErrorAttempt(e: E): IsEq[F[E Xor Unit]] = + F.attempt(F.raiseError[Unit](e)) <-> F.pure(Xor.left(e)) + + def pureAttempt[A](a: A): IsEq[F[E Xor A]] = + F.attempt(F.pure(a)) <-> F.pure(Xor.right(a)) + + def handleErrorWithConsistentWithRecoverWith[A](fa: F[A], f: E => F[A]): IsEq[F[A]] = + F.handleErrorWith(fa)(f) <-> F.recoverWith(fa)(PartialFunction(f)) + + def handleErrorConsistentWithRecover[A](fa: F[A], f: E => A): IsEq[F[A]] = + F.handleError(fa)(f) <-> F.recover(fa)(PartialFunction(f)) + + def recoverConsistentWithRecoverWith[A](fa: F[A], pf: PartialFunction[E, A]): IsEq[F[A]] = + F.recover(fa)(pf) <-> F.recoverWith(fa)(pf andThen F.pure) + + def attemptConsistentWithAttemptT[A](fa: F[A]): IsEq[XorT[F, E, A]] = + XorT(F.attempt(fa)) <-> F.attemptT(fa) +} + +object ApplicativeErrorLaws { + def apply[F[_], E](implicit ev: ApplicativeError[F, E]): ApplicativeErrorLaws[F, E] = + new ApplicativeErrorLaws[F, E] { def F: ApplicativeError[F, E] = ev } +} diff --git a/laws/src/main/scala/cats/laws/MonadErrorLaws.scala b/laws/src/main/scala/cats/laws/MonadErrorLaws.scala index 3a2acbaf44..d1b129e3a0 100644 --- a/laws/src/main/scala/cats/laws/MonadErrorLaws.scala +++ b/laws/src/main/scala/cats/laws/MonadErrorLaws.scala @@ -1,44 +1,12 @@ package cats package laws -import cats.data.{Xor, XorT} - // Taken from http://functorial.com/psc-pages/docs/Control/Monad/Error/Class/index.html -trait MonadErrorLaws[F[_], E] extends MonadLaws[F] { +trait MonadErrorLaws[F[_], E] extends ApplicativeErrorLaws[F, E] with MonadLaws[F] { implicit override def F: MonadError[F, E] def monadErrorLeftZero[A, B](e: E, f: A => F[B]): IsEq[F[B]] = F.flatMap(F.raiseError[A](e))(f) <-> F.raiseError[B](e) - - def monadErrorHandleWith[A](e: E, f: E => F[A]): IsEq[F[A]] = - F.handleErrorWith(F.raiseError[A](e))(f) <-> f(e) - - def monadErrorHandle[A](e: E, f: E => A): IsEq[F[A]] = - F.handleError(F.raiseError[A](e))(f) <-> F.pure(f(e)) - - def handleErrorWithPure[A](a: A, f: E => F[A]): IsEq[F[A]] = - F.handleErrorWith(F.pure(a))(f) <-> F.pure(a) - - def handleErrorPure[A](a: A, f: E => A): IsEq[F[A]] = - F.handleError(F.pure(a))(f) <-> F.pure(a) - - def raiseErrorAttempt(e: E): IsEq[F[E Xor Unit]] = - F.attempt(F.raiseError[Unit](e)) <-> F.pure(Xor.left(e)) - - def pureAttempt[A](a: A): IsEq[F[E Xor A]] = - F.attempt(F.pure(a)) <-> F.pure(Xor.right(a)) - - def handleErrorWithConsistentWithRecoverWith[A](fa: F[A], f: E => F[A]): IsEq[F[A]] = - F.handleErrorWith(fa)(f) <-> F.recoverWith(fa)(PartialFunction(f)) - - def handleErrorConsistentWithRecover[A](fa: F[A], f: E => A): IsEq[F[A]] = - F.handleError(fa)(f) <-> F.recover(fa)(PartialFunction(f)) - - def recoverConsistentWithRecoverWith[A](fa: F[A], pf: PartialFunction[E, A]): IsEq[F[A]] = - F.recover(fa)(pf) <-> F.recoverWith(fa)(pf andThen F.pure) - - def attemptConsistentWithAttemptT[A](fa: F[A]): IsEq[XorT[F, E, A]] = - XorT(F.attempt(fa)) <-> F.attemptT(fa) } object MonadErrorLaws { diff --git a/laws/src/main/scala/cats/laws/discipline/ApplicativeErrorTests.scala b/laws/src/main/scala/cats/laws/discipline/ApplicativeErrorTests.scala new file mode 100644 index 0000000000..581c25e3aa --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/ApplicativeErrorTests.scala @@ -0,0 +1,57 @@ +package cats +package laws +package discipline + +import cats.data.{ Xor, XorT } +import cats.laws.discipline.MonoidalTests.Isomorphisms +import cats.laws.discipline.arbitrary._ +import cats.laws.discipline.eq.unitEq +import org.scalacheck.{Arbitrary, Prop} +import org.scalacheck.Prop.forAll + +trait ApplicativeErrorTests[F[_], E] extends ApplicativeTests[F] { + def laws: ApplicativeErrorLaws[F, E] + + def applicativeError[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit + ArbFA: Arbitrary[F[A]], + ArbFB: Arbitrary[F[B]], + ArbFC: Arbitrary[F[C]], + ArbFAtoB: Arbitrary[F[A => B]], + ArbFBtoC: Arbitrary[F[B => C]], + ArbE: Arbitrary[E], + EqFA: Eq[F[A]], + EqFB: Eq[F[B]], + EqFC: Eq[F[C]], + EqE: Eq[E], + EqFXorEU: Eq[F[E Xor Unit]], + EqFXorEA: Eq[F[E Xor A]], + EqXorTFEA: Eq[XorT[F, E, A]], + EqFABC: Eq[F[(A, B, C)]], + iso: Isomorphisms[F] + ): RuleSet = { + new RuleSet { + def name: String = "applicativeError" + def bases: Seq[(String, RuleSet)] = Nil + def parents: Seq[RuleSet] = Seq(applicative[A, B, C]) + def props: Seq[(String, Prop)] = Seq( + "applicativeError handleWith" -> forAll(laws.applicativeErrorHandleWith[A] _), + "applicativeError handle" -> forAll(laws.applicativeErrorHandle[A] _), + "applicativeError handleErrorWith pure" -> forAll(laws.handleErrorWithPure[A] _), + "applicativeError handleError pure" -> forAll(laws.handleErrorPure[A] _), + "applicativeError raiseError attempt" -> forAll(laws.raiseErrorAttempt _), + "applicativeError pure attempt" -> forAll(laws.pureAttempt[A] _), + "applicativeError handleErrorWith consistent with recoverWith" -> forAll(laws.handleErrorWithConsistentWithRecoverWith[A] _), + "applicativeError handleError consistent with recover" -> forAll(laws.handleErrorConsistentWithRecover[A] _), + "applicativeError recover consistent with recoverWith" -> forAll(laws.recoverConsistentWithRecoverWith[A] _), + "applicativeError attempt consistent with attemptT" -> forAll(laws.attemptConsistentWithAttemptT[A] _) + ) + } + } +} + +object ApplicativeErrorTests { + def apply[F[_], E](implicit FE: ApplicativeError[F, E]): ApplicativeErrorTests[F, E] = + new ApplicativeErrorTests[F, E] { + def laws: ApplicativeErrorLaws[F, E] = ApplicativeErrorLaws[F, E] + } +} diff --git a/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala index 2b7ee10dfb..7714fac34e 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala @@ -4,12 +4,11 @@ package discipline import cats.data.{ Xor, XorT } import cats.laws.discipline.MonoidalTests.Isomorphisms -import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq.unitEq import org.scalacheck.{Arbitrary, Prop} import org.scalacheck.Prop.forAll -trait MonadErrorTests[F[_], E] extends MonadTests[F] { +trait MonadErrorTests[F[_], E] extends ApplicativeErrorTests[F, E] with MonadTests[F] { def laws: MonadErrorLaws[F, E] def monadError[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit @@ -32,19 +31,9 @@ trait MonadErrorTests[F[_], E] extends MonadTests[F] { new RuleSet { def name: String = "monadError" def bases: Seq[(String, RuleSet)] = Nil - def parents: Seq[RuleSet] = Seq(monad[A, B, C]) + def parents: Seq[RuleSet] = Seq(applicativeError[A, B, C], monad[A, B, C]) def props: Seq[(String, Prop)] = Seq( - "monadError left zero" -> forAll(laws.monadErrorLeftZero[A, B] _), - "monadError handleWith" -> forAll(laws.monadErrorHandleWith[A] _), - "monadError handle" -> forAll(laws.monadErrorHandle[A] _), - "monadError handleErrorWith pure" -> forAll(laws.handleErrorWithPure[A] _), - "monadError handleError pure" -> forAll(laws.handleErrorPure[A] _), - "monadError raiseError attempt" -> forAll(laws.raiseErrorAttempt _), - "monadError pure attempt" -> forAll(laws.pureAttempt[A] _), - "monadError handleErrorWith consistent with recoverWith" -> forAll(laws.handleErrorWithConsistentWithRecoverWith[A] _), - "monadError handleError consistent with recover" -> forAll(laws.handleErrorConsistentWithRecover[A] _), - "monadError recover consistent with recoverWith" -> forAll(laws.recoverConsistentWithRecoverWith[A] _), - "monadError attempt consistent with attemptT" -> forAll(laws.attemptConsistentWithAttemptT[A] _) + "monadError left zero" -> forAll(laws.monadErrorLeftZero[A, B] _) ) } } diff --git a/tests/src/test/scala/cats/tests/FreeApplicativeTests.scala b/tests/src/test/scala/cats/tests/FreeApplicativeTests.scala index aae6fdf5ca..3ac915abdd 100644 --- a/tests/src/test/scala/cats/tests/FreeApplicativeTests.scala +++ b/tests/src/test/scala/cats/tests/FreeApplicativeTests.scala @@ -4,8 +4,9 @@ package tests import cats.arrow.NaturalTransformation import cats.free.FreeApplicative import cats.laws.discipline.{MonoidalTests, ApplicativeTests, SerializableTests} -import cats.laws.discipline.eq.tuple3Eq +import cats.laws.discipline.eq.{tuple3Eq, tuple2Eq} import cats.data.Const +import cats.state.State import org.scalacheck.{Arbitrary, Gen} @@ -86,4 +87,45 @@ class FreeApplicativeTests extends CatsSuite { val fli2 = FreeApplicative.lift[List, Int](List.empty) fli2.analyze[G[Int]](countingNT) should ===(List(0)) } + + test("foldMap order of effects - regression check for #799") { + trait Foo[A] { + def getA: A + } + final case class Bar(getA: Int) extends Foo[Int] + final case class Baz(getA: Long) extends Foo[Long] + + type Dsl[A] = FreeApplicative[Foo, A] + + type Tracked[A] = State[String, A] + + val f: Foo ~> Tracked = new (Foo ~> Tracked) { + def apply[A](fa: Foo[A]): Tracked[A] = State[String, A]{ s0 => + (s0 + fa.toString + ";", fa.getA) + } + } + + val x: Dsl[Int] = FreeApplicative.lift(Bar(3)) + val y: Dsl[Long] = FreeApplicative.lift(Baz(5L)) + + val z1: Dsl[Long] = Apply[Dsl].map2(x, y)((x, y) => x.toLong + y) + val z2: Dsl[Long] = Apply[Dsl].map2(y, x)((y, x) => x.toLong + y) + + z1.foldMap(f).run("").value should === (("Bar(3);Baz(5);", 8L)) + z2.foldMap(f).run("").value should === (("Baz(5);Bar(3);", 8L)) + } + + test("analyze order of effects - regression check for #799") { + type Dsl[A] = FreeApplicative[Id, A] + val x: Dsl[String] = FreeApplicative.lift[Id, String]("x") + val y: Dsl[String] = FreeApplicative.lift[Id, String]("y") + + val z = Apply[Dsl].map2(x, y)((_, _) => ()) + + val asString: Id ~> λ[α => String] = new (Id ~> λ[α => String]) { + def apply[A](a: A): String = a.toString + } + + z.analyze(asString) should === ("xy") + } } diff --git a/tests/src/test/scala/cats/tests/StateTTests.scala b/tests/src/test/scala/cats/tests/StateTTests.scala index 12a640e90a..257f89e46e 100644 --- a/tests/src/test/scala/cats/tests/StateTTests.scala +++ b/tests/src/test/scala/cats/tests/StateTTests.scala @@ -3,40 +3,40 @@ package tests import cats.laws.discipline.{MonoidalTests, MonadStateTests, MonoidKTests, SerializableTests} import cats.state.{State, StateT} -import cats.tests.FreeTests._ import cats.laws.discipline.eq._ +import cats.laws.discipline.arbitrary._ import org.scalacheck.{Arbitrary, Gen} class StateTTests extends CatsSuite { import StateTTests._ test("basic state usage"){ - add1.run(1).run should === (2 -> 1) + add1.run(1).value should === (2 -> 1) } test("traversing state is stack-safe"){ val ns = (0 to 100000).toList val x = ns.traverseU(_ => add1) - x.runS(0).run should === (100001) + x.runS(0).value should === (100001) } test("State.pure and StateT.pure are consistent"){ forAll { (s: String, i: Int) => val state: State[String, Int] = State.pure(i) val stateT: State[String, Int] = StateT.pure(i) - state.run(s).run should === (stateT.run(s).run) + state.run(s) should === (stateT.run(s)) } } test("Monoidal syntax is usable on State") { val x = add1 *> add1 - x.runS(0).run should === (2) + x.runS(0).value should === (2) } test("Singleton and instance inspect are consistent"){ forAll { (s: String, i: Int) => - State.inspect[Int, String](_.toString).run(i).run should === ( - State.pure[Int, Unit](()).inspect(_.toString).run(i).run) + State.inspect[Int, String](_.toString).run(i) should === ( + State.pure[Int, Unit](()).inspect(_.toString).run(i)) } } @@ -109,10 +109,10 @@ class StateTTests extends CatsSuite { object StateTTests extends StateTTestsInstances { implicit def stateEq[S:Eq:Arbitrary, A:Eq]: Eq[State[S, A]] = - stateTEq[free.Trampoline, S, A] + stateTEq[Eval, S, A] implicit def stateArbitrary[S: Arbitrary, A: Arbitrary]: Arbitrary[State[S, A]] = - stateTArbitrary[free.Trampoline, S, A] + stateTArbitrary[Eval, S, A] val add1: State[Int, Int] = State(n => (n + 1, n)) } diff --git a/tests/src/test/scala/cats/tests/StreamingTTests.scala b/tests/src/test/scala/cats/tests/StreamingTTests.scala index b18beda640..6442ff3964 100644 --- a/tests/src/test/scala/cats/tests/StreamingTTests.scala +++ b/tests/src/test/scala/cats/tests/StreamingTTests.scala @@ -77,6 +77,11 @@ class StreamingTTests extends CatsSuite { } } + test("filter - check regression") { + val s = StreamingT[Option, Int](1, 2, 1) + s.filter(_ > 1).toList should === (Some(List(2))) + } + test("foldLeft with Id consistent with List.foldLeft") { forAll { (s: StreamingT[Id, Int], l: Long, f: (Long, Int) => Long) => s.foldLeft(l)(f) should === (s.toList.foldLeft(l)(f)) diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index 6d608b3b9e..d3fb5b2906 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -1,9 +1,9 @@ package cats package tests -import cats.data.{NonEmptyList, Validated, ValidatedNel, Xor} +import cats.data.{NonEmptyList, Validated, ValidatedNel, Xor, XorT} import cats.data.Validated.{Valid, Invalid} -import cats.laws.discipline.{BifunctorTests, TraverseTests, ApplicativeTests, SerializableTests, MonoidalTests} +import cats.laws.discipline.{BifunctorTests, TraverseTests, ApplicativeErrorTests, SerializableTests, MonoidalTests} import org.scalacheck.{Gen, Arbitrary} import org.scalacheck.Arbitrary._ import cats.laws.{OrderLaws, GroupLaws} @@ -17,12 +17,15 @@ class ValidatedTests extends CatsSuite { checkAll("Validated[String, Int]", MonoidalTests[Validated[String,?]].monoidal[Int, Int, Int]) checkAll("Monoidal[Validated[String,?]]", SerializableTests.serializable(Monoidal[Validated[String,?]])) - checkAll("Validated[String, Int]", ApplicativeTests[Validated[String,?]].applicative[Int, Int, Int]) checkAll("Validated[?, ?]", BifunctorTests[Validated].bifunctor[Int, Int, Int, Int, Int, Int]) - checkAll("Applicative[Validated[String,?]]", SerializableTests.serializable(Applicative[Validated[String,?]])) + + implicit val eq0 = XorT.xorTEq[Validated[String, ?], String, Int] + + checkAll("Validated[String, Int]", ApplicativeErrorTests[Validated[String, ?], String].applicativeError[Int, Int, Int]) + checkAll("ApplicativeError[Xor, String]", SerializableTests.serializable(ApplicativeError[Validated[String, ?], String])) checkAll("Validated[String, Int] with Option", TraverseTests[Validated[String,?]].traverse[Int, Int, Int, Int, Option, Option]) - checkAll("Traverse[Validated[String,?]]", SerializableTests.serializable(Traverse[Validated[String,?]])) + checkAll("Traverse[Validated[String, ?]]", SerializableTests.serializable(Traverse[Validated[String,?]])) checkAll("Validated[String, Int]", OrderLaws[Validated[String, Int]].order) checkAll("Order[Validated[String, Int]]", SerializableTests.serializable(Order[Validated[String, Int]])) diff --git a/tests/src/test/scala/cats/tests/VectorTests.scala b/tests/src/test/scala/cats/tests/VectorTests.scala index 423e101a15..a15b6c2020 100644 --- a/tests/src/test/scala/cats/tests/VectorTests.scala +++ b/tests/src/test/scala/cats/tests/VectorTests.scala @@ -1,13 +1,17 @@ package cats package tests -import cats.laws.discipline.{MonadCombineTests, SerializableTests, TraverseTests, MonoidalTests} +import cats.laws.discipline.{MonadCombineTests, CoflatMapTests, SerializableTests, TraverseTests, MonoidalTests} import cats.laws.discipline.eq.tuple3Eq class VectorTests extends CatsSuite { + checkAll("Vector[Int]", MonoidalTests[Vector].monoidal[Int, Int, Int]) checkAll("Monoidal[Vector]", SerializableTests.serializable(Monoidal[Vector])) + checkAll("Vector[Int]", CoflatMapTests[Vector].coflatMap[Int, Int, Int]) + checkAll("CoflatMap[Vector]", SerializableTests.serializable(CoflatMap[Vector])) + checkAll("Vector[Int]", MonadCombineTests[Vector].monadCombine[Int, Int, Int]) checkAll("MonadCombine[Vector]", SerializableTests.serializable(MonadCombine[Vector])) diff --git a/tests/src/test/scala/cats/tests/WordCountTest.scala b/tests/src/test/scala/cats/tests/WordCountTest.scala index 174a23329d..8324349a38 100644 --- a/tests/src/test/scala/cats/tests/WordCountTest.scala +++ b/tests/src/test/scala/cats/tests/WordCountTest.scala @@ -43,7 +43,7 @@ class WordCountTest extends CatsSuite { val wordCountState = allResults.first.first val lineCount = allResults.first.second val charCount = allResults.second - val wordCount = wordCountState.runA(false).run + val wordCount = wordCountState.runA(false).value charCount.getConst should === (96) lineCount.getConst should === (2) wordCount.getConst should === (17) diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index 17404b837f..b1e152df52 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -153,6 +153,12 @@ class XorTTests extends CatsSuite { } } + test("valueOr with Id consistent with Xor valueOr") { + forAll { (xort: XorT[Id, String, Int], f: String => Int) => + xort.valueOr(f) should === (xort.value.valueOr(f)) + } + } + test("getOrElse with Id consistent with Xor getOrElse") { forAll { (xort: XorT[Id, String, Int], i: Int) => xort.getOrElse(i) should === (xort.value.getOrElse(i))