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