ScopedValue: Capturing current bindings
Holo The Sage Wolf
holo3146 at gmail.com
Tue May 23 14:37:03 UTC 2023
What you are trying to do is possible to implement, albeit a bit convoluted
and wasteful.
Ignoring all of the details, conceptually ScopedValues are made out of 2
parts:
- The instances
- The bindings
You create instances, bind them to values, and run some code under the spe
cific bindings.
So to get the functionality you are looking for you need to wrap the SV
instances with your own object "AwareScopedValue", ASV will hold onto a
list of SV, when you bind a new value you can either override the top SV,
or create a new SV and put it on the top of the stack.
When binding a ASV you simply bind all of the SV.
When trying to get value from it, you can choose from which layer to take.
---
But I'm not sure you really need this functionality, having a public SV is
just begging for problems so *any* use of SV should be transparent to the
user, and inside of your own library/framework you can make sure that are
no double-bindings, and if you need several layers of bindings, then your
use case is not what a single SV is meant to handle.
Cheers,
Yuval Paz
On Tue, May 23, 2023, 17:07 Attila Kelemen <attila.kelemen85 at gmail.com>
wrote:
> I was contemplating on a dependency injection framework built on
> scoped values. For the sake of simplicity, I will make it trivial and
> unoptimized here. Consider the following as our mini limited DI
> framework:
>
> ```
> ScopedValue<Map<Class<?>, Supplier<?>>> FACTORIES
> = ScopedValue.newInstance();
>
> <T> void withBinding(
> Class<T> type,
> Supplier<T> supplier,
> Runnable task
> ) {
> var prevValue = FACTORIES.orElse(Collections.emptyMap());
> var newValue = new HashMap<>(prevValue);
> newValue.put(type, supplier);
>
> ScopedValue.where(FACTORIES, newValue, task);
> }
>
> <T> T getByType(Class<T> type) {
> Supplier<?> provider = FACTORIES
> .orElse(Collections.emptyMap())
> .get(type);
> if (provider == null) {
> throw new IllegalStateException();
> }
> return type.cast(provider.get());
> }
> ```
>
> A trivial use of such framework would look like this:
>
> ```
> withBinding(String.class, () -> "Hello!", () -> {
> System.out.println(getByType(String.class));
> });
> ```
>
> This would print "Hello!" as expected. However, consider the following
> case:
>
> ```
> ScopedValue<String> testValue = ScopedValue.newInstance();
>
> ScopedValue.where(testValue, "OuterValue", () -> {
> withBinding(String.class, testValue::get, () -> {
> ScopedValue.where(testValue, "InnerValue", () -> {
> System.out.println(getByType(String.class));
> });
> });
> });
> ```
>
> While we would normally expect the injection to be uninfluenced by the
> calling context (especially, since you could even use this for lazy
> singletons). However, the above would obviously print "InnerValue",
> even though that was not the case when calling `withBinding`.
>
> This is because we can't capture the bindings (at least I don't know a
> way). Not even StructuredTaskScope can be (ab)used for this, because
> it would (rightfully) refuse the `fork`, if the current scoped value
> context changed.
>
> What I wish for is a way to capture the current scoped values, and
> later execute code with the captured scoped values. For example, we
> could have a
> `ScopedValues.withCurrentContext(Consumer<CapturedScopedValueContext>)`
> (or similar , variants), where `CapturedScopedValueContext` could look
> like this (simplified for clarity):
>
> ```
> interface CapturedScopedValueContext {
> <T> T inContext(Callable<? extends T> task);
> }
> ```
>
> in which case, now we could create our safe `withBinding` variant
> relying on the current unsafe version:
>
> ```
> <T> void withBindingSafe(
> Class<T> type,
> Supplier<T> supplier,
> Runnable task
> ) {
> ScopedValues.withCurrentContext(context -> {
> withBinding(type, () -> context.inContext(supplier::get), task);
> });
> }
> ```
>
> Replacing `withBinding` with `withBindingSafe` in our "complicated"
> use-case, we would receive "OuterValue" as intended.
>
> I hope we can have something like this or similar feature (obviously
> not in JDK 21) enabling this use-case.
>
> Attila
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/loom-dev/attachments/20230523/aa3a90c6/attachment-0001.htm>
More information about the loom-dev
mailing list