On Wed, 17 Apr 2024 11:12:37 GMT, Per Minborg <pminb...@openjdk.org> 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

Reply via email to