Revisiting field references
Brian Goetz
brian.goetz at oracle.com
Tue Jun 4 23:22:11 UTC 2019
Alan;
Thanks for these thoughts. Indeed, field references are something that
keep coming up in the "if we only had" department, and your analysis
covers many of the important points. They are especially useful in APIs
such as the Equivalence one that Kevin and Liam have already written
about, as well as similar APIs (e.g., Comparator factories). Let me
make some connections with other roads not taken, as well as some
connections with other features that are being considered.
> Getter as Supplier
> Setter as Consumer
It surely seems nice to be able to SAM-convert a field ref to an
appropriately-shaped SAM type based on target typing. Another question
we should ask is: should field refs (and method refs, for that matter)
have a standalone type? In the original JSR-335 discussions, another
"left for the future" item was whether method refs should be "enhanced"
compared to lambdas. Such areas include: better equals() implementation
(two method refs for the same method could be equal), better toString()
implementation (using the name of the method, rather than an opaque
descriptor), reflective behavior as described in your Increased
Transparency section (retrieving the nominal metadata for the method
class, name, and descriptor), etc.
Two more areas that were discussed, but left for the future, for method
references were:
- Alternate target types, such as Method or MethodHandle (yes, Remi,
we see you);
- Explicit method references (with explicit parameter types, such as
`Foo::bar(int,int)`), which could be used in contexts where a SAM target
type is not available -- such as Method.
Further, the work done on constant folding exposes interesting
optimization opportunities for APIs that can truck in member references;
for a field ref such as `Holder::field`, foldable (compile-time
invocable) APIs could be brought to bear. I don't want to deep dive on
this here, other than to point out that there is a significant connection.
> More subtly, source code may
> become harder to understand when the expression this::field may mean
> two very different things, either a read or a write.
Thanks for bringing up this point, as it speaks to the "we _can_, but
should we?" question. Target typing is a powerful thing, but as you
say, it is a little scary for the same locution to represent such
different behaviors.
> We could have some sort of
> FieldReference object describing the class in which the field lives,
> the name and type of the field, and which object (if any) is bound as
> the receiver.
A few points here:
- Some of these items are security-sensitive, and others are much less
so. The name of the method or field, for example, is pretty safe to
expose (it is already exposed through stack traces), whereas exposing
bound receivers or captured arguments seems a pretty clear no-go (I
doubt anyone passing `foo::bar` to a library method thinks they are
sharing `foo` too.) I think the line here is: expose nominal metadata
(class names, method names, method descriptors), and not live objects
(class mirrors, method handles, bound arguments, etc), except as
mediated by access control (e.g., caller provides a Lookup.)
- It's not either-or; MethodReference / FieldReference could be
interfaces, and when SAM-converting a method/field reference to a
suitable SAM type, the resulting object is of type `(SamType &
MethodReference)`.
- FieldReference/MethodReference could be the standalone type of field
refs (and either non-overloaded method references, or method references
with explicit parameter types.)
> An advantage of supporting this is that it could enable libraries that
> currently accept lambdas to generate more efficient code.
This connects with the constant-folding, as well as offering some
API-design options, such as:
static<T> Comparator<T> ofFields(FieldReference... fields)
which would allow for invocations like
Comparator.ofFields(Foo::a, Foo::b)
Having a factory that takes a varargs of field references is a good
trigger to try optimizing transparently, as you know you have
optimizable references in hand.
> I hope it goes without saying that I am not proposing to actually
> implement Comparator.optimize any time soon: it’s just a convenient,
> well-known example of the kind of library that could be gradually
> improved by promoting field references from “sugar for a lambda” to
> reified objects.
A good API is both easy to use and easy to optimize. Capturing
higher-level semantics such as transparent field refs vs opaque lambdas
scores well on both counts, regardless of specific optimization avenues
you might have in mind.
> Annotation parameters
>
> Last, if we had such a FieldRef descriptor, we might like to be able
> to use them as annotation parameters, making it possible to be more
> formal about annotations like
>
> class Stream {
> private Lock lock;
> @GuardedBy(Stream::lock) // next() only called while holding lock
> public int next() {...}
> }
Method refs in annotations are another one on the "would like to do
eventually" list; adding ConstantDynamic removed one of the major
impediments, but there's still a bunch of work in between here and
there. To your comment about method refs, the main impediment is the
lack of explicitly typed method references, but we know how to do that too.
> Probably this would mean having FieldReference implement Constable, so
> that Holder::field could be put in the constant pool, along with other
> annotation parameters.
Yes. The key is that any symbolic metadata (Class, MethodType) is
mediated by a Lookup (for the reflective path), and by the implicit
resolution context (when loading the classfile.)
> This also suggests that a FieldReference object
> should not directly store the bound receiver, since that could not be
> put in the constant pool; instead we would want a FieldReference to
> always be unbound, and then some sort of decorator or wrapper that
> holds a FieldReference tied to a captured receiver.
A bound method ref is not a constant; only unbound / static method refs
would be. Same story for fields.
> Open Questions
>
> The first set of questions is: are these all reasonable, useful
> features? Am I missing any pitfalls that they imply?
>
> One looming design question is unfortunately syntax: is Foo::x really
> the best syntax? It's very natural, but it will be ambiguous if Foo
> also has a method named x.
... which will be the case very often. And in fact, that method is
probably (but we can't guarantee) an accessor for the field. And, while
we can construct precedence rules that make sense for various use cases,
in reality, sometimes you want Foo::x to be the field (such as in the
comparator example), and sometimes you want it to be the method (such as
when you're sharing it with foreign code, because you want the defensive
copy that the accessor might do.) So even if we bias towards the method
(which as you say, is a forced move), there still needs to be a way to
denote the field ref explicitly in case of conflict.
> Finally, if anyone has implementation tips I would be happy to hear
> them. I am pretty new to javac, and while I've thrown together an
> implementation that desugars field references into getter lambdas it’s
> far from a finished feature, and I’m sure what I’ve already done
> wasn't done the best way. Finding all the places that would need to
> change is no small task.
I know that Dan went through a similar exercise a long time ago,
identifying the places in the spec/compiler that would take stress if we
wanted to broaden the set of target types for method refs. While that's
not exactly the same problem, it would be useful to dredge that up, as
it will likely cover some of the same ground.
For prototyping purposes, I wouldn't try to use the same token; that's
just making your life harder. Pick something easily parsable, even if
its hideous.
More thoughts later.
More information about the amber-spec-experts
mailing list