Repository: 7mind/izumi
Pull request #863 (2020-01-28 13:49:30)

Introduce DIApplicative, relax requirements for DIResource methods (now liftF & LiftF need only Applicative, not Sync or BIO)


Patch:
diff --git a/build.sbt b/build.sbt
index c8b941958..fb6025f86 100644
--- a/build.sbt
+++ b/build.sbt
@@ -530,8 +530,7 @@ lazy val `fundamentals-json-circe` = project.in(file("fundamentals/fundamentals-
       "io.circe" %% "circe-parser" % V.circe,
       "io.circe" %% "circe-literal" % V.circe,
       "io.circe" %% "circe-generic-extras" % V.circe_generic_extras,
-      "io.circe" %% "circe-derivation" % V.circe_derivation,
-      "org.typelevel" %% "jawn-parser" % V.jawn
+      "io.circe" %% "circe-derivation" % V.circe_derivation
     )
   )
   .settings(
@@ -1674,8 +1673,7 @@ lazy val `logstage-api` = project.in(file("logstage/logstage-api"))
       compilerPlugin("org.typelevel" % "kind-projector" % V.kind_projector cross CrossVersion.full),
       "org.scala-lang.modules" %% "scala-collection-compat" % V.collection_compat,
       "org.scalatest" %% "scalatest" % V.scalatest % Test,
-      "org.scala-lang" % "scala-reflect" % scalaVersion.value % Provided,
-      "io.github.cquiroz" %% "scala-java-time" % V.scala_java_time
+      "org.scala-lang" % "scala-reflect" % scalaVersion.value % Provided
     )
   )
   .settings(
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/DIResource.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/DIResource.scala
index 7c5d787e3..ec12f01f3 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/DIResource.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/DIResource.scala
@@ -5,7 +5,7 @@ import java.util.concurrent.{ExecutorService, TimeUnit}
 import cats.effect.Bracket
 import cats.{Applicative, ~>}
 import izumi.distage.model.definition.DIResource.DIResourceBase
-import izumi.distage.model.effect.DIEffect
+import izumi.distage.model.effect.{DIApplicative, DIEffect}
 import izumi.distage.model.providers.ProviderMagnet
 import izumi.fundamentals.platform.functional.Identity
 import izumi.fundamentals.platform.language.Quirks._
@@ -164,7 +164,7 @@ object DIResource {
     }
   }
 
-  def liftF[F[_], A](acquire: => F[A])(implicit F: DIEffect[F]): DIResource[F, A] = {
+  def liftF[F[_], A](acquire: => F[A])(implicit F: DIApplicative[F]): DIResource[F, A] = {
     make(acquire)(_ => F.unit)
   }
 
@@ -239,11 +239,11 @@ object DIResource {
   }
 
   implicit final class DIResourceSimpleSyntax[A](private val resource: DIResourceBase[Identity, A]) extends AnyVal {
-    def toEffect[G[_]: DIEffect]: DIResourceBase[G, A] = {
-      new DIResourceBase[G, A] {
+    def toEffect[F[_]: DIEffect]: DIResourceBase[F, A] = {
+      new DIResourceBase[F, A] {
         override type InnerResource = resource.InnerResource
-        override def acquire: G[InnerResource] = DIEffect[G].maybeSuspend(resource.acquire)
-        override def release(res: InnerResource): G[Unit] = DIEffect[G].maybeSuspend(resource.release(res))
+        override def acquire: F[InnerResource] = DIEffect[F].maybeSuspend(resource.acquire)
+        override def release(res: InnerResource): F[Unit] = DIEffect[F].maybeSuspend(resource.release(res))
         override def extract(res: InnerResource): A = resource.extract(res)
       }
     }
@@ -256,7 +256,7 @@ object DIResource {
       * This function only makes sense in code examples or at top-level,
       * please use [[DIResourceUse#use]] instead!
       */
-    def unsafeGet()(implicit F: DIEffect[F]): F[A] = F.map(resource.acquire)(resource.extract)
+    def unsafeGet()(implicit F: DIApplicative[F]): F[A] = F.map(resource.acquire)(resource.extract)
   }
 
   trait Simple[A] extends DIResource[Identity, A]
@@ -265,7 +265,7 @@ object DIResource {
 
   trait MutableNoClose[+A] extends DIResource.SelfNoClose[Identity, A] { this: A => }
 
-  abstract class NoClose[+F[_]: DIEffect, A] extends DIResourceBase.NoClose[F, A] with DIResource[F, A]
+  abstract class NoClose[+F[_]: DIApplicative, A] extends DIResourceBase.NoClose[F, A] with DIResource[F, A]
 
   /**
     * Class-based variant of [[make]]:
@@ -312,7 +312,7 @@ object DIResource {
     *   class IntRes extends DIResource.LiftF(acquire = IO(1000))
     * }}}
     */
-  class LiftF[+F[_]: DIEffect, A] private[this](acquire0: () => F[A], @unused dummy: Boolean = false) extends NoClose[F, A] {
+  class LiftF[+F[_]: DIApplicative, A] private[this](acquire0: () => F[A], @unused dummy: Boolean = false) extends NoClose[F, A] {
     def this(acquire: => F[A]) = this(() => acquire)
 
     override final def acquire: F[A] = acquire0()
@@ -361,7 +361,7 @@ object DIResource {
     */
   class OfZIO[-R, +E, +A](inner: ZManaged[R, E, A]) extends DIResource.Of[ZIO[R, E, ?], A](fromZIO(inner))
 
-  abstract class SelfNoClose[+F[_]: DIEffect, +A] extends DIResourceBase.NoClose[F, A] { this: A =>
+  abstract class SelfNoClose[+F[_]: DIApplicative, +A] extends DIResourceBase.NoClose[F, A] { this: A =>
     override type InnerResource = Unit
     override final def extract(resource: Unit): A = this
   }
@@ -406,8 +406,8 @@ object DIResource {
   }
 
   object DIResourceBase {
-    abstract class NoClose[+F[_]: DIEffect, +A] extends DIResourceBase[F, A] {
-      override final def release(resource: InnerResource): F[Unit] = DIEffect[F].unit
+    abstract class NoClose[+F[_]: DIApplicative, +A] extends DIResourceBase[F, A] {
+      override final def release(resource: InnerResource): F[Unit] = DIApplicative[F].unit
     }
   }
 
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/effect/DIEffect.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/effect/DIEffect.scala
index 3e5c3d658..acd81e411 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/effect/DIEffect.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/effect/DIEffect.scala
@@ -1,17 +1,16 @@
 package izumi.distage.model.effect
 
 import cats.effect.ExitCase
+import izumi.distage.model.effect.LowPriorityDIApplicativeInstances._Applicative
 import izumi.distage.model.effect.LowPriorityDIEffectInstances._Sync
-import izumi.functional.bio.{BIO, BIOExit}
+import izumi.functional.bio.{BIO, BIOApplicative, BIOExit}
 import izumi.fundamentals.platform.functional.Identity
-import izumi.fundamentals.platform.language.Quirks._
+import izumi.fundamentals.platform.language.unused
 
 import scala.language.implicitConversions
 import scala.util.{Failure, Success, Try}
 
-trait DIEffect[F[_]] {
-  def pure[A](a: A): F[A]
-  def map[A, B](fa: F[A])(f: A => B): F[B]
+trait DIEffect[F[_]] extends DIApplicative[F] {
   def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]
   def bracket[A, B](acquire: => F[A])(release: A => F[Unit])(use: A => F[B]): F[B]
   def bracketCase[A, B](acquire: => F[A])(release: (A, Option[Throwable]) => F[Unit])(use: A => F[B]): F[B]
@@ -32,7 +31,6 @@ trait DIEffect[F[_]] {
 
   def fail[A](t: => Throwable): F[A]
 
-  final val unit: F[Unit] = pure(())
   final def widen[A, B >: A](fa: F[A]): F[B] = fa.asInstanceOf[F[B]]
   final def suspendF[A](effAction: => F[A]): F[A] = flatMap(maybeSuspend(effAction))(identity)
   final def traverse_[A](l: Iterable[A])(f: A => F[Unit]): F[Unit] = {
@@ -46,7 +44,7 @@ trait DIEffect[F[_]] {
 }
 
 object DIEffect extends LowPriorityDIEffectInstances {
-  def apply[F[_] : DIEffect]: DIEffect[F] = implicitly
+  @inline def apply[F[_]: DIEffect]: DIEffect[F] = implicitly
 
   object syntax {
     implicit def suspendedSyntax[F[_], A](fa: => F[A]): DIEffectSuspendedSyntax[F, A] = new DIEffectSuspendedSyntax(() => fa)
@@ -65,8 +63,9 @@ object DIEffect extends LowPriorityDIEffectInstances {
 
   implicit val diEffectIdentity: DIEffect[Identity] = new DIEffect[Identity] {
     override def pure[A](a: A): Identity[A] = a
-    override def flatMap[A, B](a: A)(f: A => Identity[B]): Identity[B] = f(a)
     override def map[A, B](fa: Identity[A])(f: A => B): Identity[B] = f(fa)
+    override def map2[A, B, C](fa: Identity[A], fb: Identity[B])(f: (A, B) => C): Identity[C] = f(fa, fb)
+    override def flatMap[A, B](a: A)(f: A => Identity[B]): Identity[B] = f(a)
 
     override def maybeSuspend[A](eff: => A): Identity[A] = eff
     override def definitelyRecover[A](fa: => Identity[A])(recover: Throwable => Identity[A]): Identity[A] = {
@@ -100,6 +99,7 @@ object DIEffect extends LowPriorityDIEffectInstances {
     new DIEffect[F[Throwable, ?]] {
       override def pure[A](a: A): F[E, A] = F.pure(a)
       override def map[A, B](fa: F[E, A])(f: A => B): F[E, B] = F.map(fa)(f)
+      override def map2[A, B, C](fa: F[E, A], fb: F[E, B])(f: (A, B) => C): F[E, C] = F.map2(fa, fb)(f)
       override def flatMap[A, B](fa: F[E, A])(f: A => F[E, B]): F[E, B] = F.flatMap(fa)(f)
 
       override def maybeSuspend[A](eff: => A): F[E, A] = F.syncThrowable(eff)
@@ -134,12 +134,12 @@ private[effect] sealed trait LowPriorityDIEffectInstances {
     *
     * Optional instance via https://blog.7mind.io/no-more-orphans.html
     */
-  implicit def fromCatsEffect[F[_], R[_[_]]](implicit l: _Sync[R], F0: R[F]): DIEffect[F] = {
-    l.discard()
+  implicit def fromCatsEffect[F[_], R[_[_]]](implicit @unused l: _Sync[R], F0: R[F]): DIEffect[F] = {
     val F = F0.asInstanceOf[cats.effect.Sync[F]]
     new DIEffect[F] {
       override def pure[A](a: A): F[A] = F.pure(a)
       override def map[A, B](fa: F[A])(f: A => B): F[B] = F.map(fa)(f)
+      override def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C] = F.map2(fa, fb)(f)
       override def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] = F.flatMap(fa)(f)
 
       override def maybeSuspend[A](eff: => A): F[A] = F.delay(eff)
@@ -179,3 +179,58 @@ object LowPriorityDIEffectInstances {
     @inline implicit final def catsEffectSync: _Sync[cats.effect.Sync] = null
   }
 }
+
+trait DIApplicative[F[_]] {
+  def pure[A](a: A): F[A]
+  def map[A, B](fa: F[A])(f: A => B): F[B]
+  def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C]
+  final val unit: F[Unit] = pure(())
+}
+
+object DIApplicative extends LowPriorityDIApplicativeInstances {
+  @inline def apply[F[_]: DIApplicative]: DIApplicative[F] = implicitly
+
+  implicit val diapplicativeIdentity: DIApplicative[Identity] = new DIApplicative[Identity] {
+    override def pure[A](a: A): Identity[A] = a
+    override def map[A, B](fa: Identity[A])(f: A => B): Identity[B] = f(fa)
+    override def map2[A, B, C](fa: Identity[A], fb: Identity[B])(f: (A, B) => C): Identity[C] = f(fa, fb)
+  }
+
+  implicit def fromBIO[F[+_, +_], E](implicit F: BIOApplicative[F]): DIApplicative[F[E, ?]] = {
+    new DIApplicative[F[E, ?]] {
+      override def pure[A](a: A): F[E, A] = F.pure(a)
+      override def map[A, B](fa: F[E, A])(f: A => B): F[E, B] = F.map(fa)(f)
+      override def map2[A, B, C](fa: F[E, A], fb: F[E, B])(f: (A, B) => C): F[E, C] = F.map2(fa, fb)(f)
+    }
+  }
+}
+
+trait LowPriorityDIApplicativeInstances {
+  /**
+    * This instance uses 'no more orphans' trick to provide an Optional instance
+    * only IFF you have cats-core as a dependency without REQUIRING a cats-core dependency.
+    *
+    * Optional instance via https://blog.7mind.io/no-more-orphans.html
+    */
+  implicit def fromCats[F[_], R[_[_]]](implicit @unused l: _Applicative[R], F0: R[F]): DIApplicative[F] = {
+    val F = F0.asInstanceOf[cats.Applicative[F]]
+    new DIApplicative[F] {
+      override def pure[A](a: A): F[A] = F.pure(a)
+      override def map[A, B](fa: F[A])(f: A => B): F[B] = F.map(fa)(f)
+      override def map2[A, B, C](fa: F[A], fb: F[B])(f: (A, B) => C): F[C] = F.map2(fa, fb)(f)
+    }
+  }
+}
+
+object LowPriorityDIApplicativeInstances {
+  /**
+    * This instance uses 'no more orphans' trick to provide an Optional instance
+    * only IFF you have cats-core as a dependency without REQUIRING a cats-core dependency.
+    *
+    * Optional instance via https://blog.7mind.io/no-more-orphans.html
+    */
+  sealed abstract class _Applicative[R[_[_]]]
+  object _Applicative {
+    @inline implicit final def catsApplicative: _Applicative[cats.Applicative] = null
+  }
+}
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/effect/package.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/effect/package.scala
index e4feae0cc..9e3ede1f3 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/effect/package.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/effect/package.scala
@@ -1,6 +1,7 @@
 package izumi.distage.model
 
 package object effect {
+  type DIApplicative2[F[_, _]] = DIApplicative[F[Throwable, ?]]
   type DIEffect2[F[_, _]] = DIEffect[F[Throwable, ?]]
   type DIEffectAsync2[F[_, _]] = DIEffectAsync[F[Throwable, ?]]
   type DIEffectRunner2[F[_, _]] = DIEffectRunner[F[Throwable, ?]]
diff --git a/distage/distage-framework/src/main/scala/izumi/distage/effect/modules/CatsDIEffectModule.scala b/distage/distage-framework/src/main/scala/izumi/distage/effect/modules/CatsDIEffectModule.scala
index ed28b557c..454fb2108 100644
--- a/distage/distage-framework/src/main/scala/izumi/distage/effect/modules/CatsDIEffectModule.scala
+++ b/distage/distage-framework/src/main/scala/izumi/distage/effect/modules/CatsDIEffectModule.scala
@@ -3,12 +3,13 @@ package izumi.distage.effect.modules
 import cats.effect.{Async, Bracket, Concurrent, ConcurrentEffect, ContextShift, Effect, ExitCode, IO, IOApp, Sync, Timer}
 import cats.{Applicative, Functor, Monad, MonadError, Parallel}
 import izumi.distage.model.definition.ModuleDef
-import izumi.distage.model.effect.{DIEffect, DIEffectAsync, DIEffectRunner}
+import izumi.distage.model.effect.{DIApplicative, DIEffect, DIEffectAsync, DIEffectRunner}
 
 object CatsDIEffectModule extends CatsDIEffectModule
 
 trait CatsDIEffectModule extends ModuleDef {
   addImplicit[DIEffectRunner[IO]]
+  addImplicit[DIApplicative[IO]]
   addImplicit[DIEffect[IO]]
 
   make[DIEffectAsync[IO]].from {
diff --git a/distage/distage-framework/src/main/scala/izumi/distage/effect/modules/IdentityDIEffectModule.scala b/distage/distage-framework/src/main/scala/izumi/distage/effect/modules/IdentityDIEffectModule.scala
index 729ef71c1..366fed9f4 100644
--- a/distage/distage-framework/src/main/scala/izumi/distage/effect/modules/IdentityDIEffectModule.scala
+++ b/distage/distage-framework/src/main/scala/izumi/distage/effect/modules/IdentityDIEffectModule.scala
@@ -1,12 +1,13 @@
 package izumi.distage.effect.modules
 
 import izumi.distage.model.definition.ModuleDef
-import izumi.distage.model.effect.{DIEffect, DIEffectAsync, DIEffectRunner}
+import izumi.distage.model.effect.{DIApplicative, DIEffect, DIEffectAsync, DIEffectRunner}
 import izumi.fundamentals.platform.functional.Identity
 
 object IdentityDIEffectModule extends IdentityDIEffectModule
 
 trait IdentityDIEffectModule extends ModuleDef {
+  addImplicit[DIApplicative[Identity]]
   addImplicit[DIEffect[Identity]]
   addImplicit[DIEffectRunner[Identity]]
   addImplicit[DIEffectAsync[Identity]]
diff --git a/distage/distage-framework/src/main/scala/izumi/distage/effect/modules/ZIODIEffectModule.scala b/distage/distage-framework/src/main/scala/izumi/distage/effect/modules/ZIODIEffectModule.scala
index ee05db2cf..a99df93c2 100644
--- a/distage/distage-framework/src/main/scala/izumi/distage/effect/modules/ZIODIEffectModule.scala
+++ b/distage/distage-framework/src/main/scala/izumi/distage/effect/modules/ZIODIEffectModule.scala
@@ -17,6 +17,7 @@ object ZIODIEffectModule extends ZIODIEffectModule
 
 trait ZIODIEffectModule extends ModuleDef {
   make[DIEffectRunner2[IO]].from[DIEffectRunner.BIOImpl[IO]]
+  addImplicit[DIApplicative2[IO]]
   addImplicit[DIEffect2[IO]]
   make[DIEffectAsync2[IO]].from(DIEffectAsync.fromBIOTemporal(_: BIOTemporal[IO]))
 
diff --git a/project/Deps.sc b/project/Deps.sc
index 480fe32af..600f3b184 100644
--- a/project/Deps.sc
+++ b/project/Deps.sc
@@ -1,4 +1,4 @@
-import $ivy.`io.7mind.izumi.sbt::sbtgen:0.0.46`
+import $ivy.`io.7mind.izumi.sbt::sbtgen:0.0.47`
 import izumi.sbtgen._
 import izumi.sbtgen.model._
 
@@ -744,7 +744,7 @@ object Izumi {
     pathPrefix = Projects.sbtplugins.basePath,
     groups = Groups.sbt,
     defaultPlatforms = Targets.jvmSbt,
-    enableSharedSettings = false,
+    enableProjectSharedAggSettings = false,
     dontIncludeInSuperAgg = false,
   )
 


Test patch:
diff --git a/distage/distage-core/src/test/scala/izumi/distage/fixtures/ResourceCases.scala b/distage/distage-core/src/test/scala/izumi/distage/fixtures/ResourceCases.scala
index b512bb66f..71981b8f1 100644
--- a/distage/distage-core/src/test/scala/izumi/distage/fixtures/ResourceCases.scala
+++ b/distage/distage-core/src/test/scala/izumi/distage/fixtures/ResourceCases.scala
@@ -158,6 +158,7 @@ object ResourceCases {
     implicit def DIEffectSuspend2[E <: Throwable]: DIEffect[Suspend2[E, ?]] = new DIEffect[Suspend2[E, ?]] {
       override def flatMap[A, B](fa: Suspend2[E, A])(f: A => Suspend2[E, B]): Suspend2[E, B] = fa.flatMap(f)
       override def map[A, B](fa: Suspend2[E, A])(f: A => B): Suspend2[E, B] = fa.map(f)
+      override def map2[A, B, C](fa: Suspend2[E, A], fb: Suspend2[E, B])(f: (A, B) => C): Suspend2[E, C] = fa.flatMap(a => fb.map(f(a, _)))
       override def pure[A](a: A): Suspend2[E, A] = Suspend2(a)
       override def fail[A](t: => Throwable): Suspend2[E, A] = Suspend2[A](throw t)
       override def maybeSuspend[A](eff: => A): Suspend2[E, A] = Suspend2(eff)
diff --git a/distage/distage-testkit-legacy/src/main/scala/izumi/distage/testkit/services/scalatest/adapter/ExternalResourceProvider.scala b/distage/distage-testkit-legacy/src/main/scala/izumi/distage/testkit/services/scalatest/adapter/ExternalResourceProvider.scala
index 998ebc931..a70099fb2 100644
--- a/distage/distage-testkit-legacy/src/main/scala/izumi/distage/testkit/services/scalatest/adapter/ExternalResourceProvider.scala
+++ b/distage/distage-testkit-legacy/src/main/scala/izumi/distage/testkit/services/scalatest/adapter/ExternalResourceProvider.scala
@@ -4,7 +4,7 @@ import distage.{DIKey, Locator, SafeType, TagK}
 import izumi.distage.bootstrap.BootstrapLocator
 import izumi.distage.model.definition.DIResource.DIResourceBase
 import izumi.distage.model.definition.{DIResource, LocatorDef}
-import izumi.distage.model.effect.{DIEffect, DIEffectAsync, DIEffectRunner}
+import izumi.distage.model.effect.{DIApplicative, DIEffect, DIEffectAsync, DIEffectRunner}
 import izumi.distage.model.provisioning.PlanInterpreter.Finalizer
 import izumi.distage.model.references.IdentifiedRef
 import izumi.distage.testkit.services.scalatest.adapter.ExternalResourceProvider.{MemoizedInstance, PreparedShutdownRuntime}
@@ -120,6 +120,7 @@ object ExternalResourceProvider {
         DIResource.liftF[Identity, Locator] {
           new LocatorDef {
             addImplicit[DIEffectRunner[Identity]]
+            addImplicit[DIApplicative[Identity]]
             addImplicit[DIEffect[Identity]]
             addImplicit[DIEffectAsync[Identity]]
           }