From 88babeeca3e926e885bc35ed6e9e82b254b693be Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 8 Dec 2015 08:32:11 -0500 Subject: [PATCH 1/5] Use SBT doctest plugin This helps ensure that our ScalaDoc examples actually compile and produce the expected result. --- build.sbt | 2 +- core/src/main/scala/cats/Foldable.scala | 30 ++++++++++++------- core/src/main/scala/cats/data/OptionT.scala | 6 ++-- core/src/main/scala/cats/data/Validated.scala | 13 ++++++-- core/src/main/scala/cats/data/Xor.scala | 6 ++-- core/src/main/scala/cats/data/XorT.scala | 21 ++++++++----- core/src/main/scala/cats/syntax/flatMap.scala | 8 ++++- project/plugins.sbt | 23 +++++++------- 8 files changed, 72 insertions(+), 37 deletions(-) diff --git a/build.sbt b/build.sbt index 600a3ff05b..e2187c24fe 100644 --- a/build.sbt +++ b/build.sbt @@ -34,7 +34,7 @@ lazy val commonSettings = Seq( compilerPlugin("org.spire-math" %% "kind-projector" % "0.6.3") ), parallelExecution in Test := false -) ++ warnUnusedImport +) ++ warnUnusedImport ++ doctestSettings lazy val commonJsSettings = Seq( scalaJSStage in Global := FastOptStage, diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 5ea749a5e6..0db2e66d95 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -87,10 +87,15 @@ import simulacrum.typeclass * For example: * * {{{ - * def parseInt(s: String): Option[Int] = ... - * val F = Foldable[List] - * F.traverse_(List("333", "444"))(parseInt) // Some(()) - * F.traverse_(List("333", "zzz"))(parseInt) // None + * scala> import cats.data.Xor + * scala> import cats.std.list._ + * scala> import cats.std.option._ + * scala> def parseInt(s: String): Option[Int] = Xor.catchOnly[NumberFormatException](s.toInt).toOption + * scala> val F = Foldable[List] + * scala> F.traverse_(List("333", "444"))(parseInt) + * res0: Option[Unit] = Some(()) + * scala> F.traverse_(List("333", "zzz"))(parseInt) + * res1: Option[Unit] = None * }}} * * This method is primarily useful when `G[_]` represents an action @@ -111,9 +116,13 @@ import simulacrum.typeclass * For example: * * {{{ - * val F = Foldable[List] - * F.sequence_(List(Option(1), Option(2), Option(3))) // Some(()) - * F.sequence_(List(Option(1), None, Option(3))) // None + * scala> import cats.std.list._ + * scala> import cats.std.option._ + * scala> val F = Foldable[List] + * scala> F.sequence_(List(Option(1), Option(2), Option(3))) + * res0: Option[Unit] = Some(()) + * scala> F.sequence_(List(Option(1), None, Option(3))) + * res1: Option[Unit] = None * }}} */ def sequence_[G[_]: Applicative, A, B](fga: F[G[A]]): G[Unit] = @@ -128,9 +137,10 @@ import simulacrum.typeclass * For example: * * {{{ - * val F = Foldable[List] - * F.foldK(List(1 :: 2 :: Nil, 3 :: 4 :: 5 :: Nil)) - * // List(1, 2, 3, 4, 5) + * scala> import cats.std.list._ + * scala> val F = Foldable[List] + * scala> F.foldK(List(1 :: 2 :: Nil, 3 :: 4 :: 5 :: Nil)) + * res0: List[Int] = List(1, 2, 3, 4, 5) * }}} */ def foldK[G[_], A](fga: F[G[A]])(implicit G: MonoidK[G]): G[A] = diff --git a/core/src/main/scala/cats/data/OptionT.scala b/core/src/main/scala/cats/data/OptionT.scala index b683b57108..24972b49ab 100644 --- a/core/src/main/scala/cats/data/OptionT.scala +++ b/core/src/main/scala/cats/data/OptionT.scala @@ -108,8 +108,10 @@ object OptionT extends OptionTInstances { * Note: The return type is a FromOptionPartiallyApplied[F], which has an apply method * on it, allowing you to call fromOption like this: * {{{ - * val t: Option[Int] = ... - * val x: OptionT[List, Int] = fromOption[List](t) + * scala> import cats.std.list._ + * scala> val o: Option[Int] = Some(2) + * scala> OptionT.fromOption[List](o) + * res0: OptionT[List, Int] = OptionT(List(Some(2))) * }}} * * The reason for the indirection is to emulate currying type parameters. diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index 4c247ee98d..aa6cf41296 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -123,6 +123,12 @@ sealed abstract class Validated[+E, +A] extends Product with Serializable { */ def map[B](f: A => B): Validated[E,B] = bimap(identity, f) + /** + * Apply a function to an Invalid value, returning a new Invalid value. + * Or, if the original valid was Valid, return it. + */ + def leftMap[EE](f: E => EE): Validated[EE,A] = bimap(f, identity) + /** * When Valid, apply the function, marking the result as valid * inside the Applicative's context, @@ -211,6 +217,7 @@ private[data] sealed abstract class ValidatedInstances extends ValidatedInstance implicit def validatedBifunctor: Bifunctor[Validated] = new Bifunctor[Validated] { override def bimap[A, B, C, D](fab: Validated[A, B])(f: A => C, g: B => D): Validated[C, D] = fab.bimap(f, g) + override def leftMap[A, B, C](fab: Validated[A, B])(f: A => C): Validated[C, B] = fab.leftMap(f) } implicit def validatedInstances[E](implicit E: Semigroup[E]): Traverse[Validated[E, ?]] with Applicative[Validated[E, ?]] = @@ -267,8 +274,10 @@ trait ValidatedFunctions { * Evaluates the specified block, catching exceptions of the specified type and returning them on the invalid side of * the resulting `Validated`. Uncaught exceptions are propagated. * - * For example: {{{ - * val result: Validated[NumberFormatException, Int] = catchOnly[NumberFormatException] { "foo".toInt } + * For example: + * {{{ + * scala> Validated.catchOnly[NumberFormatException] { "foo".toInt } + * res0: Validated[NumberFormatException, Int] = Invalid(java.lang.NumberFormatException: For input string: "foo") * }}} */ def catchOnly[T >: Null <: Throwable]: CatchOnlyPartiallyApplied[T] = new CatchOnlyPartiallyApplied[T] diff --git a/core/src/main/scala/cats/data/Xor.scala b/core/src/main/scala/cats/data/Xor.scala index ac5892198b..4333775c30 100644 --- a/core/src/main/scala/cats/data/Xor.scala +++ b/core/src/main/scala/cats/data/Xor.scala @@ -222,8 +222,10 @@ trait XorFunctions { * Evaluates the specified block, catching exceptions of the specified type and returning them on the left side of * the resulting `Xor`. Uncaught exceptions are propagated. * - * For example: {{{ - * val result: NumberFormatException Xor Int = catchOnly[NumberFormatException] { "foo".toInt } + * For example: + * {{{ + * scala> Xor.catchOnly[NumberFormatException] { "foo".toInt } + * res0: Xor[NumberFormatException, Int] = Left(java.lang.NumberFormatException: For input string: "foo") * }}} */ def catchOnly[T >: Null <: Throwable]: CatchOnlyPartiallyApplied[T] = diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 6bc5c4a23c..54145a8c00 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -120,12 +120,15 @@ final case class XorT[F[_], A, B](value: F[A Xor B]) { * * Example: * {{{ - * val v1: Validated[NonEmptyList[Error], Int] = ... - * val v2: Validated[NonEmptyList[Error], Int] = ... - * val xort: XorT[Error, Int] = ... - * - * val result: XorT[NonEmptyList[Error], Int] = - * xort.withValidated { v3 => (v1 |@| v2 |@| v3.leftMap(NonEmptyList(_))) { case (i, j, k) => i + j + k } } + * scala> import cats.std.option._ + * scala> import cats.std.list._ + * scala> import cats.syntax.apply._ + * scala> type Error = String + * scala> val v1: Validated[NonEmptyList[Error], Int] = Validated.Invalid(NonEmptyList("error 1")) + * scala> val v2: Validated[NonEmptyList[Error], Int] = Validated.Invalid(NonEmptyList("error 2")) + * scala> val xort: XorT[Option, Error, Int] = XorT(Some(Xor.left("error 3"))) + * scala> xort.withValidated { v3 => (v1 |@| v2 |@| v3.leftMap(NonEmptyList(_))).map{ case (i, j, k) => i + j + k } } + * res0: XorT[Option, NonEmptyList[Error], Int] = XorT(Some(Left(OneAnd(error 1,List(error 2, error 3))))) * }}} */ def withValidated[AA, BB](f: Validated[A, B] => Validated[AA, BB])(implicit F: Functor[F]): XorT[F, AA, BB] = @@ -148,8 +151,10 @@ trait XorTFunctions { * Note: The return type is a FromXorPartiallyApplied[F], which has an apply method * on it, allowing you to call fromXor like this: * {{{ - * val t: Xor[String, Int] = ... - * val x: XorT[Option, String, Int] = fromXor[Option](t) + * scala> import cats.std.option._ + * scala> val t: Xor[String, Int] = Xor.right(3) + * scala> XorT.fromXor[Option](t) + * res0: XorT[Option, String, Int] = XorT(Some(Right(3))) * }}} * * The reason for the indirection is to emulate currying type parameters. diff --git a/core/src/main/scala/cats/syntax/flatMap.scala b/core/src/main/scala/cats/syntax/flatMap.scala index ae444f4e15..7c8b278faa 100644 --- a/core/src/main/scala/cats/syntax/flatMap.scala +++ b/core/src/main/scala/cats/syntax/flatMap.scala @@ -34,7 +34,13 @@ final class FlatMapOps[F[_], A](fa: F[A])(implicit F: FlatMap[F]) { * you can evaluate it only ''after'' the first action has finished: * * {{{ - * fa.followedByEval(later(fb)) + * scala> import cats.Eval + * scala> import cats.std.option._ + * scala> import cats.syntax.flatMap._ + * scala> val fa: Option[Int] = Some(3) + * scala> def fb: Option[String] = Some("foo") + * scala> fa.followedByEval(Eval.later(fb)) + * res0: Option[String] = Some(foo) * }}} */ def followedByEval[B](fb: Eval[F[B]]): F[B] = F.flatMap(fa)(_ => fb.value) diff --git a/project/plugins.sbt b/project/plugins.sbt index a61f92053c..68e5161cca 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,14 +3,15 @@ resolvers += Resolver.url( url("http://dl.bintray.com/content/tpolecat/sbt-plugin-releases"))( Resolver.ivyStylePatterns) -addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.3.2") -addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.0") -addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") -addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.5.3") -addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "0.8.1") -addSbtPlugin("org.tpolecat" % "tut-plugin" % "0.4.0") -addSbtPlugin("pl.project13.scala"% "sbt-jmh" % "0.2.3") -addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "0.6.0") -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.2.0") -addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "0.8.4") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.5") +addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.3.2") +addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.0") +addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") +addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.5.3") +addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "0.8.1") +addSbtPlugin("org.tpolecat" % "tut-plugin" % "0.4.0") +addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.2.3") +addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "0.6.0") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.2.0") +addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "0.8.4") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.5") +addSbtPlugin("com.github.tkawachi" % "sbt-doctest" % "0.3.5") From d1186b1feec786ac8583438a1c04d1a9a4c76712 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 8 Dec 2015 09:30:36 -0500 Subject: [PATCH 2/5] Don't generate JS tests for sbt-doctest This was causing some build failures. Also use an explicit dependency on our version of scalacheck instead of letting sbt-doctest bring in its own version. --- build.sbt | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index e2187c24fe..f716c96070 100644 --- a/build.sbt +++ b/build.sbt @@ -18,6 +18,10 @@ lazy val buildSettings = Seq( crossScalaVersions := Seq("2.10.5", "2.11.7") ) +lazy val catsDoctestSettings = Seq( + doctestWithDependencies := false +) ++ doctestSettings + lazy val commonSettings = Seq( scalacOptions ++= commonScalacOptions, resolvers ++= Seq( @@ -34,7 +38,7 @@ lazy val commonSettings = Seq( compilerPlugin("org.spire-math" %% "kind-projector" % "0.6.3") ), parallelExecution in Test := false -) ++ warnUnusedImport ++ doctestSettings +) ++ warnUnusedImport lazy val commonJsSettings = Seq( scalaJSStage in Global := FastOptStage, @@ -43,12 +47,16 @@ lazy val commonJsSettings = Seq( lazy val commonJvmSettings = Seq( testOptions in Test += Tests.Argument(TestFrameworks.ScalaTest, "-oDF") -) +// currently sbt-doctest is only running on the JVM, because I was running into +// some issues in the generated JS tests. +) ++ catsDoctestSettings lazy val catsSettings = buildSettings ++ commonSettings ++ publishSettings ++ scoverageSettings +lazy val scalacheckVersion = "1.12.5" + lazy val disciplineDependencies = Seq( - libraryDependencies += "org.scalacheck" %%% "scalacheck" % "1.12.5", + libraryDependencies += "org.scalacheck" %%% "scalacheck" % scalacheckVersion, libraryDependencies += "org.typelevel" %%% "discipline" % "0.4" ) @@ -122,6 +130,7 @@ lazy val core = crossProject.crossType(CrossType.Pure) .settings( sourceGenerators in Compile <+= (sourceManaged in Compile).map(Boilerplate.gen) ) + .settings(libraryDependencies += "org.scalacheck" %%% "scalacheck" % scalacheckVersion % "test") .jsSettings(commonJsSettings:_*) .jvmSettings(commonJvmSettings:_*) From fbf562f3be82b0c21fbf5e48a3d1663221d548c4 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 8 Dec 2015 10:14:29 -0500 Subject: [PATCH 3/5] Reduce depth of arbitrary `Free` instances The build has been hanging during tests in the `free` module recently, and I suspect this may be the cause. --- free/src/test/scala/cats/free/FreeTests.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/free/src/test/scala/cats/free/FreeTests.scala b/free/src/test/scala/cats/free/FreeTests.scala index fb330a5a5a..bd00a5e131 100644 --- a/free/src/test/scala/cats/free/FreeTests.scala +++ b/free/src/test/scala/cats/free/FreeTests.scala @@ -73,7 +73,7 @@ sealed trait FreeTestsInstances { } implicit def freeArbitrary[F[_], A](implicit F: Arbitrary[F[A]], A: Arbitrary[A]): Arbitrary[Free[F, A]] = - Arbitrary(freeGen[F, A](6)) + Arbitrary(freeGen[F, A](4)) implicit def freeEq[S[_]: Monad, A](implicit SA: Eq[S[A]]): Eq[Free[S, A]] = new Eq[Free[S, A]] { From aaca73c36e819856a9b342f9dae5c0d8bd2ac9df Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 8 Dec 2015 10:26:36 -0500 Subject: [PATCH 4/5] Add note about doctest not working in JS builds --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index f716c96070..1ddc91a35d 100644 --- a/build.sbt +++ b/build.sbt @@ -47,8 +47,8 @@ lazy val commonJsSettings = Seq( lazy val commonJvmSettings = Seq( testOptions in Test += Tests.Argument(TestFrameworks.ScalaTest, "-oDF") -// currently sbt-doctest is only running on the JVM, because I was running into -// some issues in the generated JS tests. +// currently sbt-doctest doesn't work in JS builds, so this has to go in the +// JVM settings. https://github.com/tkawachi/sbt-doctest/issues/52 ) ++ catsDoctestSettings lazy val catsSettings = buildSettings ++ commonSettings ++ publishSettings ++ scoverageSettings From 1ae5d583d935d6d8bd7b2cce1d39f6a181a05ac6 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 8 Dec 2015 19:01:29 -0500 Subject: [PATCH 5/5] Add coreJVM/test to the buildJVM alias The sbt-doctest plugin generates tests within the `core` module. --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 1ddc91a35d..f043cfffbb 100644 --- a/build.sbt +++ b/build.sbt @@ -227,7 +227,7 @@ lazy val publishSettings = Seq( ) ++ credentialSettings ++ sharedPublishSettings ++ sharedReleaseProcess // These aliases serialise the build for the benefit of Travis-CI. -addCommandAlias("buildJVM", ";macrosJVM/compile;coreJVM/compile;freeJVM/compile;freeJVM/test;stateJVM/compile;stateJVM/test;lawsJVM/compile;testsJVM/test;jvm/test;bench/test") +addCommandAlias("buildJVM", ";macrosJVM/compile;coreJVM/compile;coreJVM/test;freeJVM/compile;freeJVM/test;stateJVM/compile;stateJVM/test;lawsJVM/compile;testsJVM/test;jvm/test;bench/test") addCommandAlias("validateJVM", ";scalastyle;buildJVM;makeSite")