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