Copying a module
Every module gets a generated copy extension function, which builds a new module from an existing one.
It takes one parameter per member, each defaulting to the binding the module currently holds for that member, so you only name what you want to override:
val app = AppModule(
config = Binding.value(Config(debug = false)),
repository = Binding.single { SqlRepository(config) },
user = Binding.factory { id -> repository().findUser(id) },
)
val debugApp = app.copy(
config = Binding.value(Config(debug = true)),
)
The original module is never modified: copy returns a new module, and app keeps its own bindings.
Calling copy() without any argument still builds a new module, with its own copy of every binding.
Copied bindings have their own state
Every binding the new module holds is a copy of the original’s: same recipe, but its own state.
A single, weakSingle or multiplex that is already built in the original starts unbuilt in the copy, and is built there, with the copy as its receiver:
val parent = MyModule(
name = Binding.value("John"),
hello = Binding.single { "Hello, $name" },
)
val child = parent.copy(
name = Binding.value("Jane"),
)
child.hello // "Hello, Jane" (1)
parent.hello // "Hello, John" (2)
| 1 | The child initializes its own single. |
| 2 | The parent’s single is untouched, and still reads the parent’s name. |
Bindings that do not keep any state, like value, provider and factory, are not duplicated: they always run against the module they are accessed through.
Detached bindings are not duplicated either: a copy keeps them, and so shares their instance.
| A module that implements the interface by hand has no bindings to copy. Its members are read through a delegate, so they keep whatever state that module has. |
To share the original’s state instead, and keep the original from ever seeing your overrides, use shadow.
|
Default implementations
For a member that has a default implementation, an omitted parameter keeps the member as it was.
If it was unbound, its default body keeps being used, and runs against the new module: it therefore observes the members you overrode in the same copy call.
Such a member additionally accepts an explicit null, which drops its binding and goes back to the interface’s own body:
val module = GreetingModule(
name = Binding.value("World"),
greeting = Binding.provider { "Hi $name" },
)
val restored = module.copy(greeting = null)
restored.greeting // "Hello, World"
Chaining copies
A copy is a module like any other, so it can be copied in turn:
val variant = app
.copy(config = Binding.value(Config(debug = true)))
.copy(user = Binding.factory { id -> User.guest(id) })
Testing shows how copy is used to build test variants of a module.
|