Repository: 7mind/izumi
Pull request #927 (2020-03-02 23:24:17)

fundamentals-bio: Move `redeem` & `flip` from `BIOError` down to `BIOMonadError` because `redeem` effectively implements `flatMap` (breaks fundamentals-bio bincompat)


Patch:
diff --git a/README.md b/README.md
index 91b42c77d..ee4c608d0 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,9 @@ including the following components:
 2. [logstage](https://izumi.7mind.io/latest/release/doc/logstage/) – Automatic structural logs from Scala string interpolations,
 3. [idealingua](https://izumi.7mind.io/latest/release/doc/idealingua/) (moved to [7mind/idealingua-v1](https://github.com/7mind/idealingua-v1)) – API Definition, Data Modeling and RPC Language, optimized for fast prototyping – like gRPC, but with a human face. Currently generates servers and clients for Go, TypeScript, C# and Scala,
 4. [Opinionated SBT plugins](https://izumi.7mind.io/latest/release/doc/sbt/) (moved to [7mind/sbtgen](https://github.com/7mind/sbtgen)) – Reduces verbosity of SBT builds and introduces new features – inter-project shared test scopes and BOM plugins (from Maven)
-5. [Percept-Plan-Execute-Repeat (PPER)](https://izumi.7mind.io/latest/release/doc/pper/) – a pattern that enables modeling very complex domains and orchestrate deadly complex processes a lot easier than you're used to.
+5. [fundamentals-bio](https://github.com/7mind/izumi/blob/develop/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/package.scala) - an ergonomic typeclass hierarchy for bifunctor effect types
+6. [izumi-reflect](https://github.com/zio/izumi-reflect) - portable, lightweight and kind-polymorphic alternative to `scala-reflect`'s Typetag for Scala, Scala.js, Scala Native and (soon) Dotty
+7. [Percept-Plan-Execute-Repeat (PPER)](https://izumi.7mind.io/latest/release/doc/pper/) – a pattern that enables modeling very complex domains and orchestrate deadly complex processes a lot easier than you're used to.
 
 Docs
 ----
diff --git a/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/modules/DockerContainerModule.scala b/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/modules/DockerContainerModule.scala
index f4f11c18c..9fa063150 100644
--- a/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/modules/DockerContainerModule.scala
+++ b/distage/distage-framework-docker/src/main/scala/izumi/distage/docker/modules/DockerContainerModule.scala
@@ -14,6 +14,8 @@ class DockerContainerModule[F[_]: TagK] extends ModuleDef {
 }
 
 object DockerContainerModule {
+  def apply[F[_]: TagK]: DockerContainerModule[F] = new DockerContainerModule[F]
+
   final val config = new ConfigModuleDef {
     makeConfig[Docker.ClientConfig]("docker")
   }
diff --git a/distage/distage-framework/src/main/scala/izumi/distage/framework/services/ModuleProvider.scala b/distage/distage-framework/src/main/scala/izumi/distage/framework/services/ModuleProvider.scala
index fc4d14666..9fc4357ec 100644
--- a/distage/distage-framework/src/main/scala/izumi/distage/framework/services/ModuleProvider.scala
+++ b/distage/distage-framework/src/main/scala/izumi/distage/framework/services/ModuleProvider.scala
@@ -1,6 +1,6 @@
 package izumi.distage.framework.services
 
-import distage.{AutoSetModule, BootstrapModule, BootstrapModuleDef, Module, TagK}
+import distage.{BootstrapModule, BootstrapModuleDef, Module}
 import izumi.distage.config.AppConfigModule
 import izumi.distage.config.model.AppConfig
 import izumi.distage.effect.modules.IdentityDIEffectModule
@@ -11,7 +11,6 @@ import izumi.distage.framework.services.ResourceRewriter.RewriteRules
 import izumi.distage.model.definition.Activation
 import izumi.distage.model.planning.{PlanMergingPolicy, PlanningHook}
 import izumi.distage.planning.extensions.GraphDumpBootstrapModule
-import izumi.distage.roles.model.AbstractRole
 import izumi.distage.roles.model.meta.RolesInfo
 import izumi.fundamentals.platform.cli.model.raw.RawAppArgs
 import izumi.logstage.api.logger.LogRouter
@@ -24,7 +23,7 @@ trait ModuleProvider {
 
 object ModuleProvider {
 
-  class Impl[F[_]: TagK]
+  class Impl
   (
     logRouter: LogRouter,
     config: AppConfig,
@@ -46,9 +45,6 @@ object ModuleProvider {
 
       val loggerModule = new LogstageModule(logRouter, true)
 
-      val collectRolesModule = AutoSetModule()
-        .register[AbstractRole[F]]
-
       val resourceRewriter = new BootstrapModuleDef {
         make[RewriteRules].fromValue(options.rewriteRules)
         many[PlanningHook]
@@ -58,7 +54,6 @@ object ModuleProvider {
       val graphvizDumpModule = if (options.addGraphVizDump) new GraphDumpBootstrapModule() else BootstrapModule.empty
 
       Seq(
-        collectRolesModule,
         roleInfoModule,
         resourceRewriter,
         loggerModule,
diff --git a/distage/distage-framework/src/main/scala/izumi/distage/roles/RoleAppLauncherImpl.scala b/distage/distage-framework/src/main/scala/izumi/distage/roles/RoleAppLauncherImpl.scala
index d30b745e2..ee537f6db 100644
--- a/distage/distage-framework/src/main/scala/izumi/distage/roles/RoleAppLauncherImpl.scala
+++ b/distage/distage-framework/src/main/scala/izumi/distage/roles/RoleAppLauncherImpl.scala
@@ -137,7 +137,7 @@ abstract class RoleAppLauncherImpl[F[_]: TagK] extends RoleAppLauncher {
   }
 
   protected def makeModuleProvider(options: PlanningOptions, parameters: RawAppArgs, activationInfo: ActivationInfo, activation: Activation, roles: RolesInfo, config: AppConfig, logRouter: LogRouter): ModuleProvider = {
-    new ModuleProvider.Impl[F](
+    new ModuleProvider.Impl(
       logRouter = logRouter,
       config = config,
       roles = roles,
diff --git a/doc/microsite/src/main/tut/distage/basics.md b/doc/microsite/src/main/tut/distage/basics.md
index 3f02b97f8..553588019 100644
--- a/doc/microsite/src/main/tut/distage/basics.md
+++ b/doc/microsite/src/main/tut/distage/basics.md
@@ -453,7 +453,7 @@ You need to use effect-aware `Injector.produceF` method to use effect bindings.
 
 distage can instantiate traits and structural types. All unimplemented fields in a trait or a refinement are filled in from the object graph.
 
-This can be used to create ZIO Environment cakes with required dependencies - https://gitter.im/ZIO/Core?at=5dbb06a86570b076740f6db2
+This can be used to create ZIO Environment cakes with required dependencies.
 
 Trait implementations are derived at compile-time by @scaladoc[TraitConstructor](izumi.distage.constructors.TraitConstructor) macro
 and can be summoned at need. 
@@ -529,6 +529,68 @@ val main = Injector()
 new zio.DefaultRuntime{}.unsafeRun(main)
 ```
 
+Any ZIO Service that requires an environment can be turned into a service without an environment dependency by providing
+the dependency in each method. This pattern can be generalized by implementing an instance of `cats.Contravariant` for your services
+and using it to turn environment dependencies into constructor parameters – that way ZIO Environment can be used uniformly
+for declaration of dependencies, but the dependencies used inside the service do not leak to other services calling it.
+Details: https://gitter.im/ZIO/Core?at=5dbb06a86570b076740f6db2
+
+Example:
+
+```scala mdoc:reset:to-string
+import cats.Contravariant
+import distage.{GCMode, Injector, ModuleDef, ProviderMagnet, Tag, TagK, TraitConstructor}
+import zio.{Task, UIO, URIO, ZIO}
+
+trait Dependee[-R] {
+def x(y: String): URIO[R, Int]
+}
+trait Depender[-R] {
+def y: URIO[R, String]
+}
+implicit val contra1: Contravariant[Dependee] = new Contravariant[Dependee] {
+  def contramap[A, B](fa: Dependee[A])(f: B => A): Dependee[B] = new Dependee[B] { def x(y: String) = fa.x(y).provideSome(f) }
+}
+implicit val contra2: Contravariant[Depender] = new Contravariant[Depender] {
+  def contramap[A, B](fa: Depender[A])(f: B => A): Depender[B] = new Depender[B] { def y = fa.y.provideSome(f) }
+}
+
+trait DependeeR { def dependee: Dependee[Any] }
+trait DependerR { def depender: Depender[Any] }
+object dependee extends Dependee[DependeeR] { def x(y: String) = ZIO.accessM(_.dependee.x(y)) }
+object depender extends Depender[DependerR] { def y            = ZIO.accessM(_.depender.y) }
+
+// cycle
+object dependerImpl extends Depender[DependeeR] {
+  def y: URIO[DependeeR, String] = dependee.x("hello").map(_.toString)
+}
+object dependeeImpl extends Dependee[DependerR] {
+  def x(y: String): URIO[DependerR, Int] = if (y == "hello") UIO(5) else depender.y.map(y.length + _.length)
+}
+
+/** Fulfill the environment dependencies of a service from the object graph */
+def fullfill[R: Tag: TraitConstructor, M[_]: TagK: Contravariant](service: M[R]): ProviderMagnet[M[Any]] = {
+  TraitConstructor[R].provider
+    .map(depsCakeR => Contravariant[M].contramap(service)(_ => depsCakeR))
+}
+
+val module = new ModuleDef {
+  make[Depender[Any]].from(fullfill(dependerImpl))
+  make[Dependee[Any]].from(fullfill(dependeeImpl))
+}
+
+Injector()
+  .produceF[Task](module, GCMode.target[Dependee[Any]])
+  .use {
+    _.run(TraitConstructor[DependeeR].provider.map {
+      (for {
+        r <- dependee.x("zxc")
+        _ <- Task(println(s"result: $r"))
+      } yield ()).provide(_)
+    })
+  }.fold(_ => 1, _ => 0)
+```
+
 If a suitable trait is specified as an implementation class for a binding, `TraitConstructor` will be used automatically:
 
 Example:
diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/BIOCatsConversions.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/BIOCatsConversions.scala
index 0342de5ea..2190f0c8d 100644
--- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/BIOCatsConversions.scala
+++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/BIOCatsConversions.scala
@@ -63,16 +63,16 @@ trait BIOCatsConversions9 {
   * 2.13 seems to overweigh this rule compared to 2.12
   * So we "disjoin" the types from each other such that they're not "related", by *checks notes*... mixing some empty traits in
   */
-private[bio] sealed trait S1
-private[bio] sealed trait S2
-private[bio] sealed trait S3
-private[bio] sealed trait S4
-private[bio] sealed trait S5
-private[bio] sealed trait S6
-private[bio] sealed trait S7
-private[bio] sealed trait S8
-private[bio] sealed trait S9
-private[bio] sealed trait S10
+private[bio] sealed trait S1 extends Any
+private[bio] sealed trait S2 extends Any
+private[bio] sealed trait S3 extends Any
+private[bio] sealed trait S4 extends Any
+private[bio] sealed trait S5 extends Any
+private[bio] sealed trait S6 extends Any
+private[bio] sealed trait S7 extends Any
+private[bio] sealed trait S8 extends Any
+private[bio] sealed trait S9 extends Any
+private[bio] sealed trait S10 extends Any
 
 object BIOCatsConversions {
 
diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/BIOSyntax.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/BIOSyntax.scala
index 641375387..74533f46d 100644
--- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/BIOSyntax.scala
+++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/BIOSyntax.scala
@@ -70,14 +70,11 @@ object BIOSyntax {
     @inline final def catchAll[E2, A2 >: A](h: E => F[E2, A2]): F[E2, A2] = F.catchAll[E, A, E2, A2](r)(h)
     @inline final def catchSome[E2 >: E, A2 >: A](h: PartialFunction[E, F[E2, A2]]): F[E2, A2] = F.catchSome[E, A, E2, A2](r)(h)
 
-    @inline final def redeem[E2, B](err: E => F[E2, B], succ: A => F[E2, B]): F[E2, B] = F.redeem[E, A, E2, B](r)(err, succ)
     @inline final def redeemPure[B](err: E => B, succ: A => B): F[Nothing, B] = F.redeemPure(r)(err, succ)
 
     @inline final def tapError[E1 >: E](f: E => F[E1, Unit]): F[E1, A] = F.tapError[E, A, E1](r)(f)
 
     @inline final def attempt: F[Nothing, Either[E, A]] = F.attempt(r)
-
-    @inline final def flip: F[A, E] = F.flip(r)
   }
 
   class BIOMonadErrorOps[F[+_, +_], E, A](override protected[this] val r: F[E, A])(implicit override protected[this] val F: BIOMonadError[F]) extends BIOErrorOps(r) {
@@ -86,7 +83,10 @@ object BIOSyntax {
 
     @inline final def flatten[E1 >: E, A1](implicit ev: A <:< F[E1, A1]): F[E1, A1] = F.flatten(r.widen)
 
+    @inline final def redeem[E2, B](err: E => F[E2, B], succ: A => F[E2, B]): F[E2, B] = F.redeem[E, A, E2, B](r)(err, succ)
+
     @inline final def leftFlatMap[E2](f: E => F[Nothing, E2]): F[E2, A] = F.leftFlatMap(r)(f)
+    @inline final def flip: F[A, E] = F.flip(r)
 
     @inline final def tapBoth[E1 >: E, E2 >: E1](err: E => F[E1, Unit])(succ: A => F[E2, Unit]): F[E2, A] = F.tapBoth[E, A, E2](r)(err, succ)
 
@@ -179,12 +179,12 @@ object BIOSyntax {
   trait BIOImplicitPuns2 extends BIOImplicitPuns3 {
     @inline implicit final def BIO[F[+_, +_]: BIO, E, A](self: F[E, A]): BIOSyntax.BIOOps[F, E, A] = new BIOSyntax.BIOOps[F, E, A](self)
     /**
-     * Shorthand for [[BIO#syncThrowable]]
-     *
-     * {{{
-     *   BIO(println("Hello world!"))
-     * }}}
-     * */
+      * Shorthand for [[BIO#syncThrowable]]
+      *
+      * {{{
+      *   BIO(println("Hello world!"))
+      * }}}
+      */
     @inline final def BIO[F[+_, +_], A](effect: => A)(implicit F: BIO[F]): F[Throwable, A] = F.syncThrowable(effect)
     @inline final def BIO[F[+_, +_]: BIO]: BIO[F] = implicitly
   }
diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/MiniBIO.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/MiniBIO.scala
index 4d6c98e9d..cb53a960f 100644
--- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/MiniBIO.scala
+++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/impl/MiniBIO.scala
@@ -30,7 +30,7 @@ import scala.language.implicitConversions
   * }}}
   * */
 sealed trait MiniBIO[+E, +A] {
-  def run(): BIOExit[E, A] = {
+  final def run(): BIOExit[E, A] = {
 
     final class Catcher[E0, A0, E1, B](
                                         val recover: BIOExit.Failure[E0] => MiniBIO[E1, B]
@@ -128,6 +128,8 @@ object MiniBIO {
       }, succ)
     }
 
+    override def catchAll[E, A, E2, A2 >: A](r: MiniBIO[E, A])(f: E => MiniBIO[E2, A2]): MiniBIO[E2, A2] = redeem(r)(f, pure)
+
     override def catchSome[E, A, E2 >: E, A2 >: A](r: MiniBIO[E, A])(f: PartialFunction[E, MiniBIO[E2, A2]]): MiniBIO[E2, A2] = {
       Redeem[E, A, E2, A2](r, {
         case BIOExit.Termination(t, e, c) => Fail.halt(BIOExit.Termination(t, e, c))
diff --git a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/package.scala b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/package.scala
index b236de382..764752f01 100644
--- a/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/package.scala
+++ b/fundamentals/fundamentals-bio/src/main/scala/izumi/functional/bio/package.scala
@@ -34,8 +34,9 @@ package object bio extends BIOSyntax {
     **/
   trait BIOFunctor[F[_, +_]] extends BIOFunctorInstances {
     def map[E, A, B](r: F[E, A])(f: A => B): F[E, B]
-    @inline def as[E, A, B](r: F[E, A])(v: => B): F[E, B] = map(r)(_ => v)
-    @inline def void[E, A](r: F[E, A]): F[E, Unit] = map(r)(_ => ())
+
+    def as[E, A, B](r: F[E, A])(v: => B): F[E, B] = map(r)(_ => v)
+    def void[E, A](r: F[E, A]): F[E, Unit] = map(r)(_ => ())
     @inline final def widen[E, A, A1](r: F[E, A])(implicit @deprecated("unused", "") ev: A <:< A1): F[E, A1] = r.asInstanceOf[F[E, A1]]
   }
 
@@ -55,7 +56,7 @@ package object bio extends BIOSyntax {
 
   trait BIOBifunctor[F[+_, +_]] extends BIOFunctor[F] {
     def bimap[E, A, E2, A2](r: F[E, A])(f: E => E2, g: A => A2): F[E2, A2]
-    @inline def leftMap[E, A, E2](r: F[E, A])(f: E => E2): F[E2, A] = bimap(r)(f, identity)
+    def leftMap[E, A, E2](r: F[E, A])(f: E => E2): F[E2, A] = bimap(r)(f, identity)
 
     @inline final def widenError[E, A, E1](r: F[E, A])(implicit @deprecated("unused","") ev: E <:< E1): F[E1, A] = r.asInstanceOf[F[E1, A]]
     @inline final def widenBoth[E, A, E1, A1](r: F[E, A])(implicit @deprecated("unused","") ev: E <:< E1, @deprecated("unused", "") ev2: A <:< A1): F[E1, A1] = r.asInstanceOf[F[E1, A1]]
@@ -76,11 +77,11 @@ package object bio extends BIOSyntax {
     def traverse[E, A, B](l: Iterable[A])(f: A => F[E, B]): F[E, List[B]]
 
     @inline final def forever[E, A](r: F[E, A]): F[E, Nothing] = *>(r, forever(r))
-    @inline def traverse_[E, A](l: Iterable[A])(f: A => F[E, Unit]): F[E, Unit] = void(traverse(l)(f))
-    @inline def sequence[E, A, B](l: Iterable[F[E, A]]): F[E, List[A]] = traverse(l)(identity)
-    @inline def sequence_[E](l: Iterable[F[E, Unit]]): F[E, Unit] = void(traverse(l)(identity))
+    def traverse_[E, A](l: Iterable[A])(f: A => F[E, Unit]): F[E, Unit] = void(traverse(l)(f))
+    def sequence[E, A, B](l: Iterable[F[E, A]]): F[E, List[A]] = traverse(l)(identity)
+    def sequence_[E](l: Iterable[F[E, Unit]]): F[E, Unit] = void(traverse(l)(identity))
 
-    @inline final val unit: F[Nothing, Unit] = pure(())
+    final val unit: F[Nothing, Unit] = pure(())
     @inline final def traverse[E, A, B](o: Option[A])(f: A => F[E, B]): F[E, Option[B]] = o match {
       case Some(a) => map(f(a))(Some(_))
       case None => pure(None)
@@ -96,52 +97,26 @@ package object bio extends BIOSyntax {
 
   trait BIOError[F[+_, +_]] extends BIOGuarantee[F] {
     def fail[E](v: => E): F[E, Nothing]
-    def redeem[E, A, E2, B](r: F[E, A])(err: E => F[E2, B], succ: A => F[E2, B]): F[E2, B]
+    def catchAll[E, A, E2, A2 >: A](r: F[E, A])(f: E => F[E2, A2]): F[E2, A2]
     def catchSome[E, A, E2 >: E, A2 >: A](r: F[E, A])(f: PartialFunction[E, F[E2, A2]]): F[E2, A2]
-//    def catchSome[E, A](r: F[_ <: E, _ <: A])(f: PartialFunction[_ <: E, F[E, A]]): F[E, A]
 
     def fromEither[E, V](effect: => Either[E, V]): F[E, V]
     def fromOption[E, A](errorOnNone: => E)(effect: => Option[A]): F[E, A]
     def fromTry[A](effect: => Try[A]): F[Throwable, A]
 
-    @inline def redeemPure[E, A, B](r: F[E, A])(err: E => B, succ: A => B): F[Nothing, B] = redeem(r)(err.andThen(pure), succ.andThen(pure))
-
-    @inline def catchAll[E, A, E2, A2 >: A](r: F[E, A])(f: E => F[E2, A2]): F[E2, A2] = redeem(r)(f, pure)
-    @inline def tapError[E, A, E1 >: E](r: F[E, A])(f: E => F[E1, Unit]): F[E1, A] = catchAll(r)(e => *>(f(e), fail(e)))
-
-    @inline def attempt[E, A](r: F[E, A]): F[Nothing, Either[E, A]] = redeemPure(r)(Left(_), Right(_))
-    @inline def flip[E, A](r: F[E, A]): F[A, E] = redeem(r)(pure, fail(_))
+    def redeemPure[E, A, B](r: F[E, A])(err: E => B, succ: A => B): F[Nothing, B] = catchAll(map(r)(succ))(e => pure(err(e)))
+    def tapError[E, A, E1 >: E](r: F[E, A])(f: E => F[E1, Unit]): F[E1, A] = catchAll(r)(e => *>(f(e), fail(e)))
+    def attempt[E, A](r: F[E, A]): F[Nothing, Either[E, A]] = redeemPure(r)(Left(_), Right(_))
 
     // defaults
-    @inline override def bimap[E, A, E2, B](r: F[E, A])(f: E => E2, g: A => B): F[E2, B] = redeem(r)(e => fail(f(e)), a => pure(g(a)))
+    override def bimap[E, A, E2, B](r: F[E, A])(f: E => E2, g: A => B): F[E2, B] = catchAll(map(r)(g))(e => fail(f(e)))
   }
 
   trait BIOMonad[F[+_, +_]] extends BIOApplicative[F] {
     def flatMap[E, A, E2 >: E, B](r: F[E, A])(f: A => F[E2, B]): F[E2, B]
     def flatten[E, A](r: F[E, F[E, A]]): F[E, A] = flatMap(r)(identity)
 
-    // defaults
-    @inline def tap[E, A, E2 >: E](r: F[E, A])(f: A => F[E2, Unit]): F[E2, A] = flatMap[E, A, E2, A](r)(a => as(f(a))(a))
-
-    @inline override def map[E, A, B](r: F[E, A])(f: A => B): F[E, B] = {
-      flatMap(r)(a => pure(f(a)))
-    }
-
-    /** execute two operations in order, return result of second operation */
-    @inline override def *>[E, A, B](f: F[E, A], next: => F[E, B]): F[E, B] = {
-      flatMap(f)(_ => next)
-    }
-
-    /** execute two operations in order, same as `*>`, but return result of first operation */
-    @inline override def <*[E, A, B](f: F[E, A], next: => F[E, B]): F[E, A] = {
-      flatMap(f)(a => map(next)(_ => a))
-    }
-
-    /** execute two operations in order, map their results */
-    @inline override def map2[E, A, B, C](r1: F[E, A], r2: => F[E, B])(f: (A, B) => C): F[E, C] = {
-      flatMap(r1)(a => map(r2)(b => f(a, b)))
-    }
-
+    def tap[E, A, E2 >: E](r: F[E, A])(f: A => F[E2, Unit]): F[E2, A] = flatMap[E, A, E2, A](r)(a => as(f(a))(a))
     @inline final def when[E, E1](cond: F[E, Boolean])(ifTrue: F[E1, Unit])(implicit ev: E <:< E1): F[E1, Unit] = {
       ifThenElse(cond)(ifTrue, unit)
     }
@@ -151,13 +126,26 @@ package object bio extends BIOSyntax {
     @inline final def ifThenElse[E, E1, A](cond: F[E, Boolean])(ifTrue: F[E1, A], ifFalse: F[E1, A])(implicit ev: E <:< E1): F[E1, A] = {
       flatMap(widenError(cond)(ev))(if (_) ifTrue else ifFalse)
     }
+
+    // defaults
+    override def map[E, A, B](r: F[E, A])(f: A => B): F[E, B] = flatMap(r)(a => pure(f(a)))
+    override def *>[E, A, B](f: F[E, A], next: => F[E, B]): F[E, B] = flatMap(f)(_ => next)
+    override def <*[E, A, B](f: F[E, A], next: => F[E, B]): F[E, A] = flatMap(f)(a => map(next)(_ => a))
+    override def map2[E, A, B, C](r1: F[E, A], r2: => F[E, B])(f: (A, B) => C): F[E, C] = flatMap(r1)(a => map(r2)(b => f(a, b)))
   }
 
   trait BIOMonadError[F[+_, +_]] extends BIOError[F] with BIOMonad[F] {
-    @inline def leftFlatMap[E, A, E2](r: F[E, A])(f: E => F[Nothing, E2]): F[E2, A] = {
+    def redeem[E, A, E2, B](r: F[E, A])(err: E => F[E2, B], succ: A => F[E2, B]): F[E2, B] = {
+      flatMap(attempt(r))(_.fold(err, succ))
+    }
+
+    def flip[E, A](r: F[E, A]): F[A, E] = {
+      redeem(r)(pure, fail(_))
+    }
+    def leftFlatMap[E, A, E2](r: F[E, A])(f: E => F[Nothing, E2]): F[E2, A] = {
       redeem(r)(e => flatMap(f(e))(fail(_)), pure)
     }
-    @inline def tapBoth[E, A, E2 >: E](r: F[E, A])(err: E => F[E2, Unit], succ: A => F[E2, Unit]): F[E2, A] = {
+    def tapBoth[E, A, E2 >: E](r: F[E, A])(err: E => F[E2, Unit], succ: A => F[E2, Unit]): F[E2, A] = {
       tap(tapError[E, A, E2](r)(err))(succ)
     }
     /** for-comprehensions sugar:
@@ -168,7 +156,7 @@ package object bio extends BIOSyntax {
       *   } yield ()
       * }}}
       */
-    @inline def withFilter[E, A](r: F[E, A])(predicate: A => Boolean)(implicit ev: NoSuchElementException <:< E): F[E, A] = {
+    def withFilter[E, A](r: F[E, A])(predicate: A => Boolean)(implicit ev: NoSuchElementException <:< E): F[E, A] = {
       flatMap(r)(a => if (predicate(a)) pure(a) else fail(new NoSuchElementException("The value doesn't satisfy the predicate")))
     }
   }
@@ -176,12 +164,12 @@ package object bio extends BIOSyntax {
   trait BIOBracket[F[+_, +_]] extends BIOMonadError[F] {
     def bracketCase[E, A, B](acquire: F[E, A])(release: (A, BIOExit[E, B]) => F[Nothing, Unit])(use: A => F[E, B]): F[E, B]
 
-    @inline def bracket[E, A, B](acquire: F[E, A])(release: A => F[Nothing, Unit])(use: A => F[E, B]): F[E, B] = {
+    def bracket[E, A, B](acquire: F[E, A])(release: A => F[Nothing, Unit])(use: A => F[E, B]): F[E, B] = {
       bracketCase[E, A, B](acquire)((a, _) => release(a))(use)
     }
 
     // defaults
-    @inline override def guarantee[E, A](f: F[E, A])(cleanup: F[Nothing, Unit]): F[E, A] = {
+    override def guarantee[E, A](f: F[E, A])(cleanup: F[Nothing, Unit]): F[E, A] = {
       bracket(unit)(_ => cleanup)(_ => f)
     }
   }
@@ -201,27 +189,25 @@ package object bio extends BIOSyntax {
   }
 
   trait BIO[F[+_, +_]] extends BIOPanic[F] {
-    type Or[+E, +A] = F[E, A]
-    type Just[+A] = F[Nothing, A]
+    final type Or[+E, +A] = F[E, A]
+    final type Just[+A] = F[Nothing, A]
 
     def syncThrowable[A](effect: => A): F[Throwable, A]
     def sync[A](effect: => A): F[Nothing, A]
 
     @inline final def apply[A](effect: => A): F[Throwable, A] = syncThrowable(effect)
 
-    // defaults
-    @inline def suspend[A](effect: => F[Throwable, A]): F[Throwable, A] = flatten(syncThrowable(effect))
+    def suspend[A](effect: => F[Throwable, A]): F[Throwable, A] = flatten(syncThrowable(effect))
 
-    @inline override def fromEither[E, A](effect: => Either[E, A]): F[E, A] = flatMap(sync(effect)) {
+    // defaults
+    override def fromEither[E, A](effect: => Either[E, A]): F[E, A] = flatMap(sync(effect)) {
       case Left(e) => fail(e): F[E, A]
       case Right(v) => pure(v): F[E, A]
     }
-
-    @inline override def fromOption[E, A](errorOnNone: => E)(effect: => Option[A]): F[E, A] = {
+    override def fromOption[E, A](errorOnNone: => E)(effect: => Option[A]): F[E, A] = {
       flatMap(sync(effect))(e => fromEither(e.toRight(errorOnNone)))
     }
-
-    @inline override def fromTry[A](effect: => Try[A]): F[Throwable, A] = {
+    override def fromTry[A](effect: => Try[A]): F[Throwable, A] = {
       syncThrowable(effect.get)
     }
   }
@@ -247,15 +233,13 @@ package object bio extends BIOSyntax {
 
     def uninterruptible[E, A](r: F[E, A]): F[E, A]
 
-
     // defaults
-    @inline def never: F[Nothing, Nothing] = async(_ => ())
+    def never: F[Nothing, Nothing] = async(_ => ())
 
-    @inline def parTraverse_[E, A, B](l: Iterable[A])(f: A => F[E, B]): F[E, Unit] = void(parTraverse(l)(f))
-    @inline def parTraverseN_[E, A, B](maxConcurrent: Int)(l: Iterable[A])(f: A => F[E, B]): F[E, Unit] = void(parTraverseN(maxConcurrent)(l)(f))
+    def parTraverse_[E, A, B](l: Iterable[A])(f: A => F[E, B]): F[E, Unit] = void(parTraverse(l)(f))
+    def parTraverseN_[E, A, B](maxConcurrent: Int)(l: Iterable[A])(f: A => F[E, B]): F[E, Unit] = void(parTraverseN(maxConcurrent)(l)(f))
 
     @inline final def fromFuture[A](mkFuture: => Future[A]): F[Throwable, A] = fromFuture(_ => mkFuture)
-
   }
 
   trait BIOTemporal[F[+_, +_]] extends BIOAsync[F] with BIOTemporalInstances {
@@ -335,4 +319,3 @@ package object bio extends BIOSyntax {
   }
 
 }
-
diff --git a/project/Versions.scala b/project/Versions.scala
index fd4851569..bd791160c 100644
--- a/project/Versions.scala
+++ b/project/Versions.scala
@@ -14,7 +14,7 @@ object V {
 
   val circe = "0.13.0"
   val circe_generic_extras = "0.13.0"
-  val circe_derivation = "0.13.0-M1"
+  val circe_derivation = "0.13.0-M2"
   val pureconfig = "0.12.3"
   val jawn = "1.0.0"
 


Test patch:
diff --git a/distage/distage-core/src/test/scala/izumi/distage/dsl/TestTagOps.scala b/distage/distage-core/src/test/scala/izumi/distage/dsl/TestTagOps.scala
index 08155226a..5319d5716 100644
--- a/distage/distage-core/src/test/scala/izumi/distage/dsl/TestTagOps.scala
+++ b/distage/distage-core/src/test/scala/izumi/distage/dsl/TestTagOps.scala
@@ -1,6 +1,5 @@
 package izumi.distage.dsl
 
-import izumi.distage.model.definition.Axis.AxisValue
 import izumi.distage.model.definition.{Axis, BindingTag}
 import izumi.fundamentals.platform.build.ExposedTestScope
 
diff --git a/distage/distage-extension-config/src/test/scala/com/github/pshirshov/configapp/TestConfigApp.scala b/distage/distage-extension-config/src/test/scala/com/github/pshirshov/configapp/TestConfigApp.scala
index a6dd43cb7..ad4f96c3d 100644
--- a/distage/distage-extension-config/src/test/scala/com/github/pshirshov/configapp/TestConfigApp.scala
+++ b/distage/distage-extension-config/src/test/scala/com/github/pshirshov/configapp/TestConfigApp.scala
@@ -4,7 +4,6 @@ import distage.Id
 import distage.config.{AppConfig, ConfigModuleDef}
 import izumi.distage.config.extractor.ConfigPathExtractor.ResolvedConfig
 import izumi.distage.model.PlannerInput
-import izumi.distage.model.definition.ModuleDef
 import izumi.fundamentals.platform.language.Quirks
 
 // application
diff --git a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/BootstrapFactory.scala b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/BootstrapFactory.scala
index 7ed56b6b4..f89ad46ce 100644
--- a/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/BootstrapFactory.scala
+++ b/distage/distage-testkit-core/src/main/scala/izumi/distage/testkit/services/dstest/BootstrapFactory.scala
@@ -24,7 +24,7 @@ object BootstrapFactory {
 
     def makeModuleProvider[F[_]: TagK](options: PlanningOptions, config: AppConfig, logRouter: LogRouter, roles: RolesInfo, activationInfo: ActivationInfo, activation: Activation): ModuleProvider = {
       // roles descriptor is not actually required there, we bind it just in case someone wish to inject a class depending on it
-      new ModuleProvider.Impl[F](
+      new ModuleProvider.Impl(
         logRouter = logRouter,
         config = config,
         roles = roles,