bye stable values, enter lazy constants
Sergey Bylokhov
bylokhov at amazon.com
Mon Sep 29 19:02:34 UTC 2025
On 9/29/25 10:07, Maurizio Cimadamore wrote:
> If this is an API, then how can we ensure constant folding in the
> absence of finality? The JVM has no idea that the LazyConstant object it
> sees on a particular read will also be the same LazyConstant object the
> next time around?
That was my point in this case the LazyConstant field is not really a constant, right? And the name
just misleading.
I think it should be mentioned in the proposal that the final keyword play important role here.
> I think "final" and "Constant" mean two different things, really.
> "final" just means (as it always did) that the field (an instance of
> LazyConstant) cannot be reassigned. This is a necessary condition for
> constant folding to occur.
The description of the JEP is similar, but it seems to focus too much on low-level optimizations.
>
> But you also need another piece -- a promise that the value associated
> with a LazyConstant changes _at most once_. That's why "Lazy", alone,
> doesn't seem sufficient. "Lazy" just means initialized before use -- can
> you also reassign the content? Maybe?
Maybe? Do we have similar notes for records? I doubt we ever had a proposal like "recordConstant" to
highlight that "record" fields cannot be reassigned.
> LazyAtMostOnce would have been more precise, but I don't think it's a
> better name :-)
I have no preferences or suggestions, but if we are talking about library then maybe just Lazy<T> is
good enough.
> Not sure about this one.
>
> Your code doesn't work as is -- you need at least a cast:
>
> ((Supplier<OrderController>)() -> new OrderController(...)).stable();
That cast can be done through some magic, but what I want to highlight is that instead of creating a
new lazy-related API, we could provide an API to mark an existing lazy values as "stable".
> This is not more readable than the proposed factory.
>
> Also, if the type of the field is "just" a Supplier, other things might
> be lost as well -- for instance, I believe the current prototype
> automatically trusts all fields of type LazyConstant to be "trusted
> final fields". This is not possible if the type of the field is just a
> Supplier. I can also imagine that Leyden condensers might be more
> interested in seeing a field with a LazyConstant type than just a
> Supplier that might come from anywhere. But, of course, we've been
> navigatig a similar space -- in the StableValue API we had the concept
> of "caching supplier":
Sooner or later, we’ll be able to trust all final values, so it’s worth keeping that in mind.
This approach might not be better than the current proposal, but it has the potential to be extended
to other data structures. For example, any API that currently accepts a Supplier (or other
functional interfaces) could accept a stable wrapper, ensuring the Supplier is executed only once.
In that sense, it’s a form of caching for pure functions.
> We could have put the factory inside Supplier (e.g.
> Supplier::ofCaching), but when we started thinking about doing just
> that, we realized that the resulting API would be less discoverable.
>
> Of course, in a minimalistic world, we don't need a LazyConstant type,
> we just add lazy suppliers, lists and maps. But we think that capturing
> what a lazy constant is, once and for all, adds value -- maybe not
> necessarily in terms of code you can write, but certainly in terms of
> readability and clearly documenting programmers intent.
Yes and no. In the current proposal, the API is split across two areas: one part is introduced in
the new LazyConstant, and another is added to existing types like Map and List.
But in fact, you only need a single 'lazy Supplier' that can be passed to already existing immutable
types like List or Map/etc, allowing them to lazily compute their values.
> True, a lazy constant field is, 99.99% of the time an implementation
> detail. It is up to the implementor to make sure the lazy constant is
> exposed correctly through public APIs.
I’m not the one comparing the Holder idiom with the JEP 526 proposal, that comparison is made in the
JEP description itself. I just pointed out that Holder have a good "property" which we will lost if
the current proposal will be used instead.
> While I understand your point re. encapsulation, cretaing a _whole new
> class_ to avoid a class-level field, doesn't seem a great trade off.
I tried reimplementing some singletons in the JDK using StableValue and compared the performance.
The results were great but on par with the Holder idiom for cold startup as well.
> I agree that a language-level keyword might push things further in this
> space -- but that's not part of the JEP under discussion.
With a new keyword it will be easier to make mistakes that are hard to fix .... finalization,
serialization, or cloning.
--
Best regards, Sergey.
More information about the leyden-dev
mailing list