Advanced Features
Dependency Pruning
distage performs pruning of all unused bindings by default. When you configure a set of “root” keys - either explicitly by passing Roots or implicitly by using Injector#produceRun or Injector#produceGet methods, distage will remove all bindings that aren’t required to create the supplied roots – these bindings will be thrown out and not even considered, much less executed.
Pruning serves two important purposes:
- It enables faster tests by omitting unused instantiations and allocations of potentially heavy resources,
- It enables multiple independent applications, aka “Roles” to be hosted within a single binary.
Example:
import distage.{Roots, ModuleDef, Injector}
class A(b: B) {
println("A!")
}
class B() {
println("B!")
}
class C() {
println("C!")
}
def module = new ModuleDef {
make[A]
make[B]
make[C]
}
// create an object graph from description in `module`
// with `A` as the GC root
val objects = Injector().produce(module, Roots.target[A]).unsafeGet()
// B!
// A!
// objects: izumi.fundamentals.platform.functional.package.Identity[izumi.distage.model.Locator] = Locator izumi.distage.LocatorDefaultImpl@2993abfc with 2 parent(s)
// with exposed keys:
// - {type.MdocSession::MdocApp0::B}
// - {type.MdocSession::MdocApp0::A}
// with confined keys: ø
// A and B are in the object graph
objects.find[A]
// res1: Option[A] = Some(repl.MdocSession$MdocApp0$A@5dd5d8b3)
objects.find[B]
// res2: Option[B] = Some(repl.MdocSession$MdocApp0$B@2e0c18ef)
// C is missing
objects.find[C]
// res3: Option[C] = None
Class C was removed because neither B nor A depended on it. It’s neither present in the Locator nor the "C!" message from it’s constructor was ever printed.
If you add c: C parameter to class B , like class B(c: C) - then C will be instantiated, because A - the “root”, will now depend on B, and B will depend on C.
class B(c: C) {
println("B!")
}
val objects = Injector().produce(module, Roots.target[A]).unsafeGet()
// C!
// B!
// A!
// objects: izumi.fundamentals.platform.functional.package.Identity[Locator] = Locator izumi.distage.LocatorDefaultImpl@591b3abd with 2 parent(s)
// with exposed keys:
// - {type.MdocSession::MdocApp4::C}
// - {type.MdocSession::MdocApp4::B}
// - {type.MdocSession::MdocApp4::A}
// with confined keys: ø
objects.find[C]
// res5: Option[C] = Some(repl.MdocSession$MdocApp4$C@459c0643)
Circular Dependencies Support
distage automatically resolves arbitrary circular dependencies, including self-references:
import distage.{DIKey, Roots, ModuleDef, Injector}
class A(val b: B)
class B(val a: A)
class C(val c: C)
def module = new ModuleDef {
make[A]
make[B]
make[C]
}
val objects = Injector().produce(module, Roots(DIKey[A], DIKey[C])).unsafeGet()
// objects: izumi.fundamentals.platform.functional.package.Identity[izumi.distage.model.Locator] = Locator izumi.distage.LocatorDefaultImpl@2a643723 with 2 parent(s)
// with exposed keys:
// - {type.MdocSession::MdocApp6::B}
// - {proxyref.{type.MdocSession::MdocApp6::B}}
// - {type.MdocSession::MdocApp6::C}
// - {proxyref.{type.MdocSession::MdocApp6::C}}
// - {type.MdocSession::MdocApp6::A}
// - {proxyinit.{type.MdocSession::MdocApp6::C}}
// - {proxyinit.{type.MdocSession::MdocApp6::B}}
// with confined keys: ø
objects.get[A] eq objects.get[B].a
// res7: Boolean = true
objects.get[B] eq objects.get[A].b
// res8: Boolean = true
objects.get[C] eq objects.get[C].c
// res9: Boolean = true
Automatic Resolution with generated proxies
The above strategy depends on distage-core-proxy-bytebuddy module and is enabled by default.
If you want to disable it, use NoProxies bootstrap configuration:
Injector.NoProxies()
// res10: Injector[izumi.fundamentals.platform.functional.package.Identity] = izumi.distage.InjectorDefaultImpl@423d6fa2
Proxies are not supported on Scala.js.
Manual Resolution with by-name parameters
Most cycles can be resolved without proxies, using By-Name parameters:
import distage.{DIKey, Roots, ModuleDef, Injector}
class A(b0: => B) {
def b: B = b0
}
class B(a0: => A) {
def a: A = a0
}
class C(self: => C) {
def c: C = self
}
def module = new ModuleDef {
make[A]
make[B]
make[C]
}
// disable proxies and execute the module
val locator = Injector.NoProxies()
.produce(module, Roots(DIKey[A], DIKey[C]))
.unsafeGet()
// locator: izumi.fundamentals.platform.functional.package.Identity[izumi.distage.model.Locator] = Locator izumi.distage.LocatorDefaultImpl@8e86051 with 2 parent(s)
// with exposed keys:
// - {type.MdocSession::MdocApp11::B}
// - {proxyref.{type.MdocSession::MdocApp11::B}}
// - {type.MdocSession::MdocApp11::C}
// - {proxyref.{type.MdocSession::MdocApp11::C}}
// - {type.MdocSession::MdocApp11::A}
// - {proxyinit.{type.MdocSession::MdocApp11::C}}
// - {proxyinit.{type.MdocSession::MdocApp11::B}}
// with confined keys: ø
assert(locator.get[A].b eq locator.get[B])
assert(locator.get[B].a eq locator.get[A])
assert(locator.get[C].c eq locator.get[C])
The proxy generation via bytebuddy is enabled by default, because in scenarios with extreme late-binding cycles can emerge unexpectedly, out of control of the origin module.
Note: Currently a limitation applies to by-names - ALL dependencies of a class engaged in a by-name circular dependency must be by-name, otherwise distage will revert to generating proxies.
Weak Sets
Set bindings can contain weak references. References designated as weak will be retained only if there are other dependencies on the referred bindings, NOT if there’s a dependency only on the entire Set.
Example:
import distage.{Roots, ModuleDef, Injector}
sealed trait Elem
final case class Strong() extends Elem {
println("Strong constructed")
}
final case class Weak() extends Elem {
println("Weak constructed")
}
def module = new ModuleDef {
make[Strong]
make[Weak]
many[Elem]
.ref[Strong]
.weak[Weak]
}
// Designate Set[Elem] as the garbage collection root,
// everything that Set[Elem] does not strongly depend on will be garbage collected
// and will not be constructed.
val roots = Roots.target[Set[Elem]]
// roots: Roots = Of(NESet({type.scala.collection.immutable.Set[repl.MdocSession.MdocApp15.Elem]}))
val objects = Injector().produce(module, roots).unsafeGet()
// Strong constructed
// objects: izumi.fundamentals.platform.functional.package.Identity[izumi.distage.model.Locator] = Locator izumi.distage.LocatorDefaultImpl@46f6c1e9 with 2 parent(s)
// with exposed keys:
// - {type.MdocSession::MdocApp15::Strong}
// - {set.{type.Set[=MdocSession::MdocApp15::Elem]}/{type.MdocSession::MdocApp15::Strong}#impl:72492860}
// - {type.Set[=MdocSession::MdocApp15::Elem]}
// with confined keys: ø
// Strong is around
objects.find[Strong]
// res16: Option[Strong] = Some(Strong())
// Weak is not
objects.find[Weak]
// res17: Option[Weak] = None
// There's only Strong in the Set
objects.get[Set[Elem]]
// res18: Set[Elem] = Set(Strong())
The Weak class was not required by any dependency of Set[Elem], so it was pruned. The Strong class remained, because the reference to it was strong, so it was counted as a dependency of Set[Elem].
If we change Strong to depend on the Weak, then Weak will be retained:
final class Strong(weak: Weak) extends Elem {
println("Strong constructed")
}
val objects = Injector().produce(module, roots).unsafeGet()
// Weak constructed
// Strong constructed
// objects: izumi.fundamentals.platform.functional.package.Identity[izumi.distage.model.Locator] = Locator izumi.distage.LocatorDefaultImpl@24479d7f with 2 parent(s)
// with exposed keys:
// - {type.MdocSession::MdocApp19::Weak}
// - {type.MdocSession::MdocApp19::Strong}
// - {set.{type.Set[=MdocSession::MdocApp19::Elem]}/{type.MdocSession::MdocApp19::Weak}#impl:-1275558662}
// - {set.{type.Set[=MdocSession::MdocApp19::Elem]}/{type.MdocSession::MdocApp19::Strong}#impl:1052012979}
// - {type.Set[=MdocSession::MdocApp19::Elem]}
// with confined keys: ø
// Weak is around
objects.find[Weak]
// res20: Option[Weak] = Some(repl.MdocSession$MdocApp19$Weak@1e481dd6)
// both Strong and Weak are in the Set
objects.get[Set[Elem]]
// res21: Set[Elem] = Set(repl.MdocSession$MdocApp19$Weak@1e481dd6, repl.MdocSession$MdocApp19$Strong@7a46052b)
Auto-Sets
Auto-Set PlanningHooks can traverse the plan and collect all bindings with implementation types that are _ <: T into a Set[T] set binding available for summoning. Filters, in addition to filtering by subtype, may be passed to AutoSetHook or to AutoSetModule.register.
import distage.{AutoSetModule, BootstrapModule, ModuleDef, Injector, Identity}
class PrintService(
name: String
) {
def start(): Unit = println(s"$name started")
}
trait A
class AImpl extends PrintService("A") with A
class B(val a: A) extends PrintService("B")
class C(val b: B) extends PrintService("C")
def bootstrapModule: BootstrapModule = new AutoSetModule {
register[PrintService](weak = false)
}
def appModule = new ModuleDef {
make[A].from[AImpl]
make[B]
make[C]
}
val services: Set[PrintService] = Injector[Identity](bootstrapOverrides = Seq(bootstrapModule))
.produceGet[Set[PrintService]](appModule)
.unsafeGet()
// services: Set[PrintService] = Set(repl.MdocSession$MdocApp22$AImpl@7858c65c, repl.MdocSession$MdocApp22$B@131867d3, repl.MdocSession$MdocApp22$C@406ca416)
require(services.size == 3)
services.foreach(_.start())
// A started
// B started
// C started
Calling .foreach on an auto-set is safe: the actions will be executed in dependency order, Auto-Sets preserve ordering, unlike user-defined Sets e.g. When C depends on B depends on A, autoset order is: A, B, C, to start call: A, B, C, to close call: C, B, A. When using an auto-set for finalization, you must .reverse the autoset.
The weak parameter controls whether Auto-Set should depend weakly or strongly on the bindings it matches. If weak = true, auto-set will only contain bindings that are retained by another GC root.
Example:
def weakAutoSetModule: BootstrapModule = new AutoSetModule {
register[PrintService](weak = true)
}
val servicesDepsOfB: Set[PrintService] = Injector[Identity](bootstrapOverrides = Seq(weakAutoSetModule))
.produceRun(appModule) {
(_: B, set: Set[PrintService]) =>
set
}
// servicesDepsOfB: Set[PrintService] = Set(repl.MdocSession$MdocApp22$AImpl@3705736c, repl.MdocSession$MdocApp22$B@15a8a86)
require(servicesDepsOfB.size == 2)
servicesDepsOfB.foreach(_.start())
// A started
// B started
val servicesDepsOfNothing: Set[PrintService] = Injector[Identity](bootstrapOverrides = Seq(weakAutoSetModule))
.produceGet[Set[PrintService]](appModule)
.unsafeGet()
// servicesDepsOfNothing: Set[PrintService] = Set()
require(servicesDepsOfNothing.isEmpty)
Further reading:
- MacWire calls the same concept “Multi Wiring”
Depending on Locator
Objects can depend on the outer object graph that contains them (Locator), by including a LocatorRef parameter:
import distage.{DIKey, ModuleDef, LocatorRef, Injector, Roots}
class A(
objects: LocatorRef
) {
def c = objects.get.get[C]
}
class B
class C
def module = new ModuleDef {
make[A]
make[B]
make[C]
}
val objects = Injector().produce(module, Roots(DIKey[A], DIKey[B], DIKey[C])).unsafeGet()
// objects: izumi.fundamentals.platform.functional.package.Identity[izumi.distage.model.Locator] = Locator izumi.distage.LocatorDefaultImpl@6e19647e with 2 parent(s)
// with exposed keys:
// - {type.MdocSession::MdocApp28::B}
// - {type.MdocSession::MdocApp28::C}
// - {type.MdocSession::MdocApp28::A}
// with confined keys: ø
// A took C from the object graph
objects.get[A].c
// res29: C = repl.MdocSession$MdocApp28$C@31bc597
// this C is the same C as in this `objects` value
val thisC = objects.get[C]
// thisC: C = repl.MdocSession$MdocApp28$C@31bc597
val thatC = objects.get[A].c
// thatC: C = repl.MdocSession$MdocApp28$C@31bc597
assert(thisC == thatC)
Locator contains metadata about the plan, and the bindings from which it was ultimately created:
import distage.{Plan, ModuleBase}
// Plan that created this locator
val plan: Plan = objects.plan
// plan: Plan = 1: {type.LocatorRef} BindingOrigin((advanced-features.md:373)) := locator {type.LocatorRef} // required for {type.MdocSession::MdocApp28::A}
// 2: {type.MdocSession::MdocApp28::B} BindingOrigin((advanced-features.md:374)) := call(π:Constructor(): MdocSession::MdocApp28::B[32m) {}
// 3: {type.MdocSession::MdocApp28::C} BindingOrigin((advanced-features.md:375)) := call(π:Constructor(): MdocSession::MdocApp28::C[32m) {}
// 4: {type.MdocSession::MdocApp28::A} BindingOrigin((advanced-features.md:373)) :=
// 5: call(π:Constructor(LocatorRef): MdocSession::MdocApp28::A[32m) {
// 6: arg objects: LocatorRef <- {type.LocatorRef}
// 7: }
// Bindings from which the Plan was built (after GC)
val bindings: ModuleBase = plan.definition
// bindings: ModuleBase =
// make[{type.repl.MdocSession.MdocApp28.B}].from(call(π:Constructor(): repl.MdocSession::MdocApp28::B)) (BindingOrigin((advanced-features.md:374)))
// make[{type.repl.MdocSession.MdocApp28.C}].from(call(π:Constructor(): repl.MdocSession::MdocApp28::C)) (BindingOrigin((advanced-features.md:375)))
// make[{type.repl.MdocSession.MdocApp28.A}].from(call(π:Constructor(izumi.distage.model.recursive.LocatorRef): repl.MdocSession::MdocApp28::A)) (BindingOrigin((advanced-features.md:373)))
Directly depending on Locator is a low-level API. If you only need to wire a subgraph within a larger object graph, you may be able to do this using a high-level Subcontexts API.
Injector inheritance
You may run a new planning cycle, inheriting the instances from an existing Locator into your new object subgraph:
val childInjector = Injector.inherit(objects)
// childInjector: Injector[[A]izumi.fundamentals.platform.functional.package.Identity[A]] = izumi.distage.InjectorDefaultImpl@38475298
class Printer(a: A, b: B, c: C) {
def printEm(): Unit =
println(s"I've got A=$a, B=$b, C=$c, all here!")
}
childInjector.produceRun(new ModuleDef { make[Printer] }) {
(_: Printer).printEm()
}
// I've got A=repl.MdocSession$MdocApp28$A@7169d575, B=repl.MdocSession$MdocApp28$B@7544d9e7, C=repl.MdocSession$MdocApp28$C@31bc597, all here!
// res31: izumi.fundamentals.platform.functional.package.Identity[Unit] = ()
It’s safe, performance-wise, to run Injector to create nested graphs – Injector is extremely fast.
Bootloader
The plan and bindings in Locator are saved in the state they were AFTER Garbage Collection has been performed. Objects can request the original input via a PlannerInput parameter:
import distage.{Roots, ModuleDef, PlannerInput, Injector, Activation}
class InjectionInfo(val plannerInput: PlannerInput)
def module = new ModuleDef {
make[InjectionInfo]
}
val input = PlannerInput(module, Roots.target[InjectionInfo], Activation.empty)
// input: PlannerInput = PlannerInput(
// make[{type.repl.MdocSession.MdocApp32.InjectionInfo}].from(call(π:Constructor(izumi.distage.model.PlannerInput): repl.MdocSession::MdocApp32::InjectionInfo)) (BindingOrigin((advanced-features.md:436))),Of(NESet({type.repl.MdocSession.MdocApp32.InjectionInfo})),Activation(Map()),PublicByDefault)
val injectionInfo = Injector().produce(input).unsafeGet().get[InjectionInfo]
// injectionInfo: InjectionInfo = repl.MdocSession$MdocApp32$InjectionInfo@64ef30d6
// the PlannerInput in `InjectionInfo` is the same as `input`
assert(injectionInfo.plannerInput == input)
Bootloader is another summonable parameter that contains the above information in aggregate and lets you create another object graph from the same inputs as the current or with alterations.
Inner Classes and Path-Dependent Types
Path-dependent types with a value prefix will be instantiated normally:
import distage.{Roots, ModuleDef, Injector}
class Path {
class A
}
val path = new Path
// path: Path = repl.MdocSession$MdocApp34$Path@14c55a8b
def module = new ModuleDef {
make[path.A]
}
Injector()
.produce(module, Roots.Everything)
.use(_.get[path.A])
// res35: path.A = repl.MdocSession$MdocApp34$Path$A@140af21d
Since version 0.10, support for types with a non-value prefix (type projections) has been dropped.
However, there’s a gotcha with value prefixes, when seen by distage they’re based on the literal variable name of the prefix, not the full type information available to the compiler, therefore the following usage, a simple rename, will fail:
def pathModule(p: Path) = new ModuleDef {
make[p.A]
}
val path1 = new Path
// path1: Path = repl.MdocSession$MdocApp34$Path@32406dce
val path2 = new Path
// path2: Path = repl.MdocSession$MdocApp34$Path@47ba5a89
Try {
Injector().produceRun(pathModule(path1) ++ pathModule(path2)) {
(p1a: path1.A, p2a: path2.A) =>
println((p1a, p2a))
}
}.isFailure
// res36: Boolean = true
This will fail because while path1.A and p.A inside new ModuleDef are the same type as far as Scala is concerned, the variables path1 & p are spelled differently and this causes a mismatch in distage.
There’s one way to workaround this - turn the type member A into a type parameter using the Aux Pattern, and then for that type parameter in turn, summon the type information using Tag implicit (as described in Tagless Final Style chapter) and summon the constructor using the ClassConstructor implicit, example:
import distage.{ClassConstructor, ModuleDef, Injector, Tag}
object Path {
type Aux[A0] = Path { type A = A0 }
}
def pathModule[A: Tag: ClassConstructor](p: Path.Aux[A]) = new ModuleDef {
make[A]
}
val path1 = new Path
// path1: Path = repl.MdocSession$MdocApp37$Path@40cc9b01
val path2 = new Path
// path2: Path = repl.MdocSession$MdocApp37$Path@410606f2
Injector().produceRun(pathModule(path1) ++ pathModule(path2)) {
(p1a: path1.A, p2a: path2.A) =>
println((p1a, p2a))
}
// (repl.MdocSession$MdocApp37$Path$Inner@61a8c58e,repl.MdocSession$MdocApp37$Path$Inner@4dec25)
// res38: izumi.fundamentals.platform.functional.package.Identity[Unit] = ()
Now the example works, because the A type inside pathModule(path1) is path1.A and for pathModule(path2) it’s path2.A, which matches their subsequent spelling in (p1a: path1.A, p2a: path2.A) => in produceRun
Locator-private bindings
You may use locator-private bindings, which will not be exposed inLocator produced by the interpreter.
If the instance is locator-private, it cannot be seen by inherited locators. There are 3 locator privacy (LocatorPrivacy) modes: - PublicByDefault - PrivateByDefault - PublicRoots
Public By Default
All the bindings are public, unless explicitly marked as confined This is default behaviour.
import distage.{Injector, LocatorPrivacy, ModuleDef, PlannerInput}
class A
class B
class C
def module = new ModuleDef {
make[A]
make[B].confined
}
val input = PlannerInput.everything(module).withLocatorPrivacy(LocatorPrivacy.PublicByDefault)
// input: PlannerInput = PlannerInput(
// make[{type.repl.MdocSession.MdocApp39.A}].from(call(π:Constructor(): repl.MdocSession::MdocApp39::A)) (BindingOrigin((advanced-features.md:567)))
// make[{type.repl.MdocSession.MdocApp39.B}].from(call(π:Constructor(): repl.MdocSession::MdocApp39::B)).tagged(Set(Confined)) (BindingOrigin((advanced-features.md:568))),Everything,Activation(Map()),PublicByDefault)
val mainLocator = Injector().produce(Injector().planUnsafe(input)).unsafeGet()
// mainLocator: izumi.fundamentals.platform.functional.package.Identity[izumi.distage.model.Locator] = Locator izumi.distage.LocatorDefaultImpl@164d1245 with 2 parent(s)
// with exposed keys:
// - {type.InjectorFactory}
// - {type.PlannerInput}
// - {type.Activation}
// - {type.BootstrapModule}
// - {id.Module@defaultModule}
// - {type.Clock1[=λ %1:0 → 1:0]}
// - {type.Entropy1[=λ %1:0 → 1:0]}
// - {type.QuasiApplicative[=λ %1:0 → 1:0]}
// - {type.QuasiAsync[=λ %1:0 → 1:0]}
// - {type.QuasiFunctor[=λ %1:0 → 1:0]}
// - {type.QuasiIORunner[=λ %1:0 → 1:0]}
// - {type.QuasiIO[=λ %1:0 → 1:0]}
// - {type.QuasiPrimitives[=λ %1:0 → 1:0]}
// - {type.QuasiTemporal[=λ %1:0 → 1:0]}
// - {type.HKTag[=(Object {type Arg = λ %2:0 → 2:0})]}
// - {type.MdocSession::MdocApp39::A}
// - {type.Bootloader}
// with confined keys:
// - {type.MdocSession::MdocApp39::B}
val inheritedInjector = Injector.inherit(mainLocator)
// inheritedInjector: Injector[[A]izumi.fundamentals.platform.functional.package.Identity[A]] = izumi.distage.InjectorDefaultImpl@4fd6e579
def module2 = new ModuleDef {
make[C]
}
val input2 = PlannerInput.everything(module2)
// input2: PlannerInput = PlannerInput(
// make[{type.repl.MdocSession.MdocApp39.C}].from(call(π:Constructor(): repl.MdocSession::MdocApp39::C)) (BindingOrigin((advanced-features.md:582))),Everything,Activation(Map()),PublicByDefault)
val inheritedLocator = inheritedInjector.produce(inheritedInjector.planUnsafe(input2)).unsafeGet()
// inheritedLocator: izumi.fundamentals.platform.functional.package.Identity[izumi.distage.model.Locator] = Locator izumi.distage.LocatorDefaultImpl@2a7abf3f with 3 parent(s)
// with exposed keys:
// - {type.InjectorFactory}
// - {type.PlannerInput}
// - {type.Activation}
// - {type.BootstrapModule}
// - {id.Module@defaultModule}
// - {type.MdocSession::MdocApp39::C}
// - {type.Bootloader}
// with confined keys: ø
assert(inheritedLocator.find[A].nonEmpty)
assert(inheritedLocator.find[C].nonEmpty)
// inherited and new binding are available
assert(inheritedLocator.find[B].isEmpty)
// but confined binding is not
Private By Default
All the bindings are private, unless explicitly marked as exposed
import distage.{Injector, LocatorPrivacy, ModuleDef, PlannerInput}
class A
class B
class C
def module = new ModuleDef {
make[A]
make[B].exposed
}
val input = PlannerInput.everything(module).withLocatorPrivacy(LocatorPrivacy.PrivateByDefault)
// input: PlannerInput = PlannerInput(
// make[{type.repl.MdocSession.MdocApp43.A}].from(call(π:Constructor(): repl.MdocSession::MdocApp43::A)) (BindingOrigin((advanced-features.md:620)))
// make[{type.repl.MdocSession.MdocApp43.B}].from(call(π:Constructor(): repl.MdocSession::MdocApp43::B)).tagged(Set(Exposed)) (BindingOrigin((advanced-features.md:621))),Everything,Activation(Map()),PrivateByDefault)
val mainLocator = Injector().produce(Injector().planUnsafe(input)).unsafeGet()
// mainLocator: izumi.fundamentals.platform.functional.package.Identity[izumi.distage.model.Locator] = Locator izumi.distage.LocatorDefaultImpl@2b0d8fb3 with 2 parent(s)
// with exposed keys:
// - {type.MdocSession::MdocApp43::B}
// with confined keys:
// - {type.InjectorFactory}
// - {type.PlannerInput}
// - {type.Activation}
// - {type.BootstrapModule}
// - {id.Module@defaultModule}
// - {type.Clock1[=λ %1:0 → 1:0]}
// - {type.Entropy1[=λ %1:0 → 1:0]}
// - {type.QuasiApplicative[=λ %1:0 → 1:0]}
// - {type.QuasiAsync[=λ %1:0 → 1:0]}
// - {type.QuasiFunctor[=λ %1:0 → 1:0]}
// - {type.QuasiIORunner[=λ %1:0 → 1:0]}
// - {type.QuasiIO[=λ %1:0 → 1:0]}
// - {type.QuasiPrimitives[=λ %1:0 → 1:0]}
// - {type.QuasiTemporal[=λ %1:0 → 1:0]}
// - {type.HKTag[=(Object {type Arg = λ %2:0 → 2:0})]}
// - {type.MdocSession::MdocApp43::A}
// - {type.Bootloader}
val inheritedInjector = Injector.inherit(mainLocator)
// inheritedInjector: Injector[[A]izumi.fundamentals.platform.functional.package.Identity[A]] = izumi.distage.InjectorDefaultImpl@2d9f12eb
def module2 = new ModuleDef {
make[C]
}
val input2 = PlannerInput.everything(module2)
// input2: PlannerInput = PlannerInput(
// make[{type.repl.MdocSession.MdocApp43.C}].from(call(π:Constructor(): repl.MdocSession::MdocApp43::C)) (BindingOrigin((advanced-features.md:635))),Everything,Activation(Map()),PublicByDefault)
val inheritedLocator = inheritedInjector.produce(inheritedInjector.planUnsafe(input2)).unsafeGet()
// inheritedLocator: izumi.fundamentals.platform.functional.package.Identity[izumi.distage.model.Locator] = Locator izumi.distage.LocatorDefaultImpl@322d3f1b with 3 parent(s)
// with exposed keys:
// - {type.InjectorFactory}
// - {type.PlannerInput}
// - {type.Activation}
// - {type.BootstrapModule}
// - {id.Module@defaultModule}
// - {type.MdocSession::MdocApp43::C}
// - {type.Bootloader}
// with confined keys: ø
assert(inheritedLocator.find[A].isEmpty)
// binding is not available
assert(inheritedLocator.find[B].nonEmpty)
assert(inheritedLocator.find[C].nonEmpty)
// but new and 'exposed' are
Public Roots
Only planning GC roots are public This is default behaviour for bootstrap injectors. All bootstrap bindings, which need to be available in inherited locators, must be explicitly marked as exposed
import distage.{Injector, LocatorPrivacy, ModuleDef, PlannerInput}
class A
class B
class C
def module = new ModuleDef {
make[A]
make[B]
}
val input = PlannerInput.target[A](module).withLocatorPrivacy(LocatorPrivacy.PublicRoots)
// input: PlannerInput = PlannerInput(
// make[{type.repl.MdocSession.MdocApp47.B}].from(call(π:Constructor(): repl.MdocSession::MdocApp47::B)) (BindingOrigin((advanced-features.md:674)))
// make[{type.repl.MdocSession.MdocApp47.A}].from(call(π:Constructor(): repl.MdocSession::MdocApp47::A)) (BindingOrigin((advanced-features.md:673))),Of(NESet({type.repl.MdocSession.MdocApp47.A})),Activation(Map()),PublicRoots)
val mainLocator = Injector().produce(Injector().planUnsafe(input)).unsafeGet()
// mainLocator: izumi.fundamentals.platform.functional.package.Identity[izumi.distage.model.Locator] = Locator izumi.distage.LocatorDefaultImpl@1cde5fe9 with 2 parent(s)
// with exposed keys:
// - {type.MdocSession::MdocApp47::A}
// with confined keys: ø
val inheritedInjector = Injector.inherit(mainLocator)
// inheritedInjector: Injector[[A]izumi.fundamentals.platform.functional.package.Identity[A]] = izumi.distage.InjectorDefaultImpl@34f5819b
def module2 = new ModuleDef {
make[C]
}
val input2 = PlannerInput.everything(module2)
// input2: PlannerInput = PlannerInput(
// make[{type.repl.MdocSession.MdocApp47.C}].from(call(π:Constructor(): repl.MdocSession::MdocApp47::C)) (BindingOrigin((advanced-features.md:688))),Everything,Activation(Map()),PublicByDefault)
val inheritedLocator = inheritedInjector.produce(inheritedInjector.planUnsafe(input2)).unsafeGet()
// inheritedLocator: izumi.fundamentals.platform.functional.package.Identity[izumi.distage.model.Locator] = Locator izumi.distage.LocatorDefaultImpl@9b76524 with 3 parent(s)
// with exposed keys:
// - {type.InjectorFactory}
// - {type.PlannerInput}
// - {type.Activation}
// - {type.BootstrapModule}
// - {id.Module@defaultModule}
// - {type.MdocSession::MdocApp47::C}
// - {type.Bootloader}
// with confined keys: ø
assert(inheritedLocator.find[A].nonEmpty)
// targeted binding is available
assert(inheritedLocator.find[B].isEmpty)
// but unrelated one is not