From 231e0053f561cf69fcb2f1930aa58494dbc61591 Mon Sep 17 00:00:00 2001 From: Kai <450507+neko-kai@users.noreply.github.com> Date: Sun, 20 Oct 2019 14:41:36 +0100 Subject: [PATCH] Fix #94, add catz.core object with implicits that require only cats-core, not cats-effect (#95) * Fix #94, add catz.core object with implicits that require only cats-core, not cats-effect - also update README.md * include core-only-test in tests on CI --- README.md | 28 +++++++++- build.sbt | 27 +++++++-- .../zio/interop/test/CoreSummonSpec.scala | 55 +++++++++++++++++++ .../src/main/scala/zio/interop/cats.scala | 26 +++++++-- .../main/scala/zio/interop/catszmanaged.scala | 20 ++++--- .../main/scala/zio/stream/interop/cats.scala | 20 ++++--- 6 files changed, 148 insertions(+), 28 deletions(-) create mode 100644 core-only-test/shared/src/test/scala/zio/interop/test/CoreSummonSpec.scala diff --git a/README.md b/README.md index b48eeb3c..e05d00d7 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ This library provides instances required by Cats Effect. -## `IO` Cats Effect's instances +## `ZIO` Cats Effect instances **ZIO** integrates with Typelevel libraries by providing an instance of `ConcurrentEffect` for `IO` as required, for instance, by `fs2`, `doobie` and `http4s`. Actually, I lied a little bit, it is not possible to implement `ConcurrentEffect` for any error type since `ConcurrentEffect` extends `MonadError` of `Throwable`. @@ -17,6 +17,22 @@ For convenience we have defined an alias as follow: Therefore, we provide an instance of `ConcurrentEffect[Task]`. +## ConcurrentEffect + +In order to get a `ConcurrentEffect[Task]` or `ConcurrentEffect[RIO[R, *]]` we need an implicit `Runtime[R]` in scope. The easiest way to get it is using `ZIO.runtime`: + +```scala +import cats.effect._ +import zio._ +import zio.interop.catz._ + +def getCE = { + ZIO.runtime.map { implicit r: Runtime[Any] => + val F: ConcurrentEffect[Task] = implicitly + } +} +``` + ### Timer In order to get a `cats.effect.Timer[Task]` instance we need an extra import: @@ -27,6 +43,16 @@ import zio.interop.catz.implicits._ The reason it is not provided by the default "interop" import is that it makes testing programs that require timing capabilities hard so an extra import wherever needed makes reasoning about it much easier. +### cats-core + +If you only need instances for `cats-core` typeclasses, not `cats-effect` import `zio.interop.catz.core._`: + +````scala +import zio.interop.catz.core._ +```` + +Note that this library only has an `Optional` dependency on cats-effect – if you or your libraries don't depend on it, this library will not add it to the classpath. + ### Example The following example shows how to use ZIO with Doobie (a library for JDBC access) and FS2 (a streaming library), which both rely on Cats Effect instances: diff --git a/build.sbt b/build.sbt index 9ea15876..fce58695 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ -import sbtcrossproject.CrossPlugin.autoImport.crossProject -import explicitdeps.ExplicitDepsPlugin.autoImport.moduleFilterRemoveValue import BuildHelper._ +import explicitdeps.ExplicitDepsPlugin.autoImport.moduleFilterRemoveValue +import sbtcrossproject.CrossPlugin.autoImport.crossProject name := "interop-cats" @@ -30,8 +30,8 @@ ThisBuild / publishTo := sonatypePublishToBundle.value addCommandAlias("fmt", "all scalafmtSbt scalafmt test:scalafmt") addCommandAlias("check", "all scalafmtSbtCheck scalafmtCheck test:scalafmtCheck") -addCommandAlias("testJVM", ";interopCatsJVM/test") -addCommandAlias("testJS", ";interopCatsJS/test") +addCommandAlias("testJVM", ";interopCatsJVM/test;coreOnlyTestJVM/test") +addCommandAlias("testJS", ";interopCatsJS/test;coreOnlyTestJS/test") lazy val root = project .in(file(".")) @@ -72,3 +72,22 @@ lazy val interopCatsJS = interopCats.js .settings( libraryDependencies += "io.github.cquiroz" %%% "scala-java-time" % "2.0.0-RC3" % Test ) + +lazy val coreOnlyTest = crossProject(JSPlatform, JVMPlatform) + .in(file("core-only-test")) + .dependsOn(interopCats) + .settings(stdSettings("core-only-test")) + .settings(skip in publish := true) + .settings( + libraryDependencies ++= Seq( + "org.typelevel" %%% "cats-core" % "2.0.0" % Test, + "dev.zio" %%% "zio-test-sbt" % "1.0.0-RC15" % Test + ) + ) + .settings(testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework")) + +lazy val coreOnlyTestJVM = coreOnlyTest.jvm +lazy val coreOnlyTestJS = coreOnlyTest.js + .settings( + libraryDependencies += "io.github.cquiroz" %%% "scala-java-time" % "2.0.0-RC3" % Test + ) diff --git a/core-only-test/shared/src/test/scala/zio/interop/test/CoreSummonSpec.scala b/core-only-test/shared/src/test/scala/zio/interop/test/CoreSummonSpec.scala new file mode 100644 index 00000000..18d67602 --- /dev/null +++ b/core-only-test/shared/src/test/scala/zio/interop/test/CoreSummonSpec.scala @@ -0,0 +1,55 @@ +package zio.interop.test + +import cats.data.NonEmptyList +import cats.{ Bifunctor, Monad, MonadError, SemigroupK } +import zio._ +import zio.interop.catz.core._ +import zio.stream.interop.catz.core._ +import zio.stream.{ Stream, ZStream } +import zio.test.Assertion._ +import zio.test.{ DefaultRunnableSpec, test, _ } + +object CoreSummonSpec + extends DefaultRunnableSpec( + suite("summons from catz.core work with only a cats-core dependency")( + test("ZIO instances") { + val monad = implicitly[Monad[UIO]] + val monadError = implicitly[MonadError[Task, Throwable]] + val semigroupK = implicitly[SemigroupK[IO[NonEmptyList[Unit], ?]]] + val bifunctor = implicitly[Bifunctor[IO]] + + monad.map(ZIO.unit)(identity) + monadError.map(ZIO.unit)(identity) + semigroupK.combineK(ZIO.unit, ZIO.unit) + bifunctor.leftMap(ZIO.fromOption(None))(identity) + + assert((), anything) + }, + test("ZManaged instances") { + val monad = implicitly[Monad[ZManaged[Any, Nothing, ?]]] + val monadError = implicitly[MonadError[Managed[Throwable, ?], Throwable]] + val semigroupK = implicitly[SemigroupK[Managed[Nothing, ?]]] + val bifunctor = implicitly[Bifunctor[Managed]] + + monad.map(ZManaged.unit)(identity) + monadError.map(ZManaged.unit)(identity) + semigroupK.combineK(ZManaged.unit, ZManaged.unit) + bifunctor.leftMap(ZManaged.fail(()))(identity) + + assert((), anything) + }, + test("ZStream instances") { + val monad = implicitly[Monad[ZStream[Any, Nothing, ?]]] + val monadError = implicitly[MonadError[Stream[Throwable, ?], Throwable]] + val semigroupK = implicitly[SemigroupK[Stream[Nothing, ?]]] + val bifunctor = implicitly[Bifunctor[Stream]] + + monad.map(ZStream.unit)(identity) + monadError.map(ZStream.unit)(identity) + semigroupK.combineK(ZStream.unit, ZStream.unit) + bifunctor.leftMap(ZStream.fail(()))(identity) + + assert((), anything) + } + ) + ) diff --git a/interop-cats/shared/src/main/scala/zio/interop/cats.scala b/interop-cats/shared/src/main/scala/zio/interop/cats.scala index beca7944..29e7ed03 100644 --- a/interop-cats/shared/src/main/scala/zio/interop/cats.scala +++ b/interop-cats/shared/src/main/scala/zio/interop/cats.scala @@ -26,12 +26,17 @@ import zio.clock.Clock import scala.concurrent.ExecutionContext import scala.concurrent.duration.{ FiniteDuration, NANOSECONDS, TimeUnit } -object catz extends CatsPlatform { +object catz extends CatsEffectPlatform { + object core extends CatsPlatform object mtl extends CatsMtlPlatform object test extends CatsTestFunctions } -abstract class CatsPlatform extends CatsInstances with CatsZManagedInstances with CatsZManagedSyntax { +abstract class CatsEffectPlatform + extends CatsEffectInstances + with CatsEffectZManagedInstances + with CatsZManagedInstances + with CatsZManagedSyntax { val console: interop.console.cats.type = interop.console.cats trait CatsApp extends App { @@ -55,7 +60,10 @@ abstract class CatsPlatform extends CatsInstances with CatsZManagedInstances wit } } -abstract class CatsInstances extends CatsInstances1 { +abstract class CatsPlatform extends CatsInstances with CatsZManagedInstances + +abstract class CatsEffectInstances extends CatsInstances with CatsEffectInstances1 { + implicit def zioContextShift[R, E]: ContextShift[ZIO[R, E, *]] = new ContextShift[ZIO[R, E, *]] { override def shift: ZIO[R, E, Unit] = ZIO.yieldNow @@ -80,6 +88,15 @@ abstract class CatsInstances extends CatsInstances1 { implicit def taskEffectInstance[R](implicit runtime: Runtime[R]): effect.ConcurrentEffect[RIO[R, *]] = new CatsConcurrentEffect[R](runtime) +} + +sealed trait CatsEffectInstances1 { + implicit def taskConcurrentInstance[R]: effect.Concurrent[RIO[R, *]] = + new CatsConcurrent[R] +} + +abstract class CatsInstances extends CatsInstances1 { + implicit def monoidKInstance[R, E: Monoid]: MonoidK[ZIO[R, E, *]] = new CatsMonoidK[R, E] @@ -91,9 +108,6 @@ abstract class CatsInstances extends CatsInstances1 { sealed abstract class CatsInstances1 extends CatsInstances2 { - implicit def taskConcurrentInstance[R]: effect.Concurrent[RIO[R, *]] = - new CatsConcurrent[R] - implicit def parallelInstance[R, E]: Parallel.Aux[ZIO[R, E, *], ParIO[R, E, *]] = new CatsParallel[R, E](monadErrorInstance) diff --git a/interop-cats/shared/src/main/scala/zio/interop/catszmanaged.scala b/interop-cats/shared/src/main/scala/zio/interop/catszmanaged.scala index 5ed18e48..94b56ddd 100644 --- a/interop-cats/shared/src/main/scala/zio/interop/catszmanaged.scala +++ b/interop-cats/shared/src/main/scala/zio/interop/catszmanaged.scala @@ -80,6 +80,18 @@ final class ZManagedSyntax[R, E, A](private val managed: ZManaged[R, E, A]) exte } +trait CatsEffectZManagedInstances { + + implicit def liftIOZManagedInstances[R]( + implicit ev: LiftIO[ZIO[R, Throwable, ?]] + ): LiftIO[ZManaged[R, Throwable, ?]] = + new LiftIO[ZManaged[R, Throwable, ?]] { + override def liftIO[A](ioa: CIO[A]): ZManaged[R, Throwable, A] = + ZManaged.fromEffect(ev.liftIO(ioa)) + } + +} + trait CatsZManagedInstances extends CatsZManagedInstances1 { implicit def monadErrorZManagedInstances[R, E]: MonadError[ZManaged[R, E, ?], E] = @@ -92,14 +104,6 @@ trait CatsZManagedInstances extends CatsZManagedInstances1 { override def combine(x: ZManaged[R, E, A], y: ZManaged[R, E, A]): ZManaged[R, E, A] = x.zipWith(y)(ev.combine) } - implicit def liftIOZManagedInstances[R, A]( - implicit ev: LiftIO[ZIO[R, Throwable, ?]] - ): LiftIO[ZManaged[R, Throwable, ?]] = - new LiftIO[ZManaged[R, Throwable, ?]] { - override def liftIO[A](ioa: CIO[A]): ZManaged[R, Throwable, A] = - ZManaged.fromEffect(ev.liftIO(ioa)) - } - } sealed trait CatsZManagedInstances1 { diff --git a/interop-cats/shared/src/main/scala/zio/stream/interop/cats.scala b/interop-cats/shared/src/main/scala/zio/stream/interop/cats.scala index 87e7d241..fff7c6e7 100644 --- a/interop-cats/shared/src/main/scala/zio/stream/interop/cats.scala +++ b/interop-cats/shared/src/main/scala/zio/stream/interop/cats.scala @@ -21,33 +21,35 @@ import cats.arrow._ import zio._ import zio.stream._ -object catz extends CatsInstances +object catz extends CatsInstances { + object core extends CatsInstances +} sealed abstract class CatsInstances extends CatsInstances1 { - implicit def alternativeInstance[R, E]: Alternative[ZStream[R, E, *]] = + implicit def zstreamAlternativeInstance[R, E]: Alternative[ZStream[R, E, *]] = new CatsAlternative[R, E] } sealed abstract class CatsInstances1 extends CatsInstances2 { - implicit def monoidKInstance[R, E]: MonoidK[ZStream[R, E, *]] = + implicit def zstreamMonoidKInstance[R, E]: MonoidK[ZStream[R, E, *]] = new CatsMonoidK[R, E] - implicit def bifunctorInstance[R]: Bifunctor[ZStream[R, *, *]] = + implicit def zstreamBifunctorInstance[R]: Bifunctor[ZStream[R, *, *]] = new CatsBifunctor[R] {} - implicit def zioArrowInstance[E]: ArrowChoice[ZStream[*, E, *]] = new CatsArrow[E] + implicit def zstreamArrowInstance[E]: ArrowChoice[ZStream[*, E, *]] = new CatsArrow[E] } sealed abstract class CatsInstances2 extends CatsInstances3 { - implicit def parallelInstance[R, E]: Parallel.Aux[ZStream[R, E, *], ParStream[R, E, *]] = - new CatsParallel[R, E](monadErrorInstance) + implicit def zstreamParallelInstance[R, E]: Parallel.Aux[ZStream[R, E, *], ParStream[R, E, *]] = + new CatsParallel[R, E](zstreamMonadErrorInstance) - implicit def semigroupKInstance[R, E: Semigroup]: SemigroupK[ZStream[R, E, *]] = + implicit def zstreamSemigroupKInstance[R, E: Semigroup]: SemigroupK[ZStream[R, E, *]] = new CatsSemigroupK[R, E] } sealed abstract class CatsInstances3 { - implicit def monadErrorInstance[R, E]: MonadError[ZStream[R, E, *], E] = + implicit def zstreamMonadErrorInstance[R, E]: MonadError[ZStream[R, E, *], E] = new CatsMonadError[R, E] }