RFR: 8330465: Stable Values and Collections (Internal)
Maurizio Cimadamore
mcimadamore at openjdk.org
Tue May 14 14:14:25 UTC 2024
On Wed, 17 Apr 2024 11:12:37 GMT, Per Minborg <pminborg at 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
More information about the hotspot-compiler-dev
mailing list