On Wed, 17 Apr 2024 11:12:37 GMT, Per Minborg <[email protected]> wrote:
>> Yes, consider the 3 capture scenarios:
>> | API | Capture frequency | Capture Impact | Code Convenience | Flexibility |
>> |-----|-------------------|----------------|------------------|-------------|
>> | `StableValue.ofMap(map, k -> ...)` | By accident | single capture is
>> reused | OK | One generator for all keys |
>> | `StableValue.computeIfUnset(map, key, k -> ...)` | By accident | capture
>> happens for all access sites | somewhat ugly | Different generator for
>> different keys |
>> | `map.get(k).computeIfUnset(() -> ...)` | Always | capture happens for all
>> access sites | OK | Different generator for different keys |
>>
>> Notice the `ofMap` factory is the most tolerant to faulty captures: even if
>> it captures, the single capturing lambda is reused for all map stables,
>> avoiding capture overheads at access sites. Given Java compiler doesn't tell
>> user anything about captures during compilation, I think `ofMap` is a better
>> factory to avoid accidentally writing capturing lambdas.
>
> I see what you mean. Initially, I thought it would be easy to create
> memorized functions but it turned out, that was not the case if one wants to
> retain easy debugability etc. So, I have added a couple of factory methods
> including this:
>
>
> /**
> * {@return a new <em>memoized</em> {@linkplain Function } backed by an
> internal
> * stable map with the provided {@code inputs} keys where the provided
> * {@code original} Function will only be invoked at most once per
> distinct input}
> *
> * @param original the original Function to convert to a memoized Function
> * @param inputs the potential input values to the Function
> * @param <T> the type of input values
> * @param <R> the return type of the function
> */
> static <T, R> Function<T, R> ofFunction(Set<? extends T> inputs,
> Function<? super T, ? extends R>
> original) {
> Objects.requireNonNull(inputs);
> Objects.requireNonNull(original);
> ...
> }
I agree that these method appear to be confusing. We have:
StableValue::of()
StableValue::ofList(int)
StableValue::ofMap(Set)
These methods are clearly primitives, because they are used to create a wrapper
around a stable value/array. (Actually, if you squint, the primitive is really
the `ofMap` factory, since that one can be used to derive the other two as
well, but that's mostly a sophism).
Everything else falls in the "helper" bucket. That is, we could have:
StableValue::ofList(IntFunction) -> List<V> // similar to
StableValue::ofList(int)
StableValue::ofMap(Function) -> Map<K, V> // similar to StableValue::ofMap(Set)
Or, we could have:
StableValue::ofSupplier(Supplier) -> Supplier<V> // similar to StableValue::of()
StableValue::ofIntFunction(IntFunction) -> IntFunction<V> // similar to
StableValue::ofList(int)
StableValue::ofFunction(Function) -> Function<K, V> // similar to
StableValue::ofMap(Set)
IMHO, having both sets feel slightly redundant. That is, if you have a Map<K,
V>, you also have a function from K, V - namely, map::get. And, conversely, if
a client wants a List<V> of fixed size, which is populated lazily, using a
memoized IntFunction is, effectively, the same thing.
-------------
PR Review Comment: https://git.openjdk.org/jdk/pull/18794#discussion_r1568840269