Improving Method Reference Ergonomics
Brian Goetz
brian.goetz at oracle.com
Mon Dec 9 17:40:45 UTC 2024
The design center for lambdas and method reference is that the key
concept is _functional interfaces_, which can be thought of as nominal
function types.
Both lambdas and method references are expressions that evaluate to
instances of functional interfaces. When you have a method
m(Supplier<Foo> s), you can create s with a lambda, a method or
constructor references, with an anonymous subclass of Supplier, with an
instance of a named subclass of Supplier, etc. Once you have a
Supplier, how it came into being is irrelevant.
Saying that conversion from method references to functional interface
instances carries extra metadata (e.g., some extra interface like
IAmAMethodRef, from which you could get the symbolic information about
what method it refers to), is a viable feature idea. However, a method
like m(Supplier<Foo> s) that really wants a method ref, has no way to
express this in the method signature. This means that if I pass it
`m(() -> new Foo(7))`, when `m` tries to cast its supplier to
IAmAMethodRef, it would get a CCE or similar. THis is what I mean by a
dynamic failure -- you wrote a program which conforms to the static type
system, but you still managed to pass "bad data" to a method.
Now, maybe you want more, not only for method refs to be scrutable, but
to add some new types, like ConstructorRef<T>. Then we could claw back
static type safety, at the cost of more complexity, and potential
ambiguity (What if there is an overload between m(Supplier<T>) and
m(ConstructorRef<T>, should the language try to resolve that?)
All of this is to say, this is a viable part of the design space, but
the incremental expressiveness is lower than you might think, and the
incremental cost and complexity is higher than you might think.
On 12/9/2024 12:30 PM, Peter Eastham wrote:
> Hey Brian,
>
> I'm hesitant to respond further, as I'd like some time to do a proper
> write up that can provide a more solid context for discussion than my
> simple example provided.
>
> You are right in that "createQuery(Customer::new)" would be behaving
> around the Constructor, instead of utilizing it as a
> "Supplier<Customer>". The exact type this would use is not something I
> have fully considered, and I agree that the current Functional
> Interfaces aren't sharp enough to restrict it to only Method
> References. It's also not a solution to have Method References resolve
> to their own type without being able to still work in those situations.
>
> When you say Dynamic Failure, can you explain that a bit more? My
> assumption is that there it would be an issue if you dynamically
> transform the class, specifically the method in the reference. However
> I'm unsure if that is any different of a failure than if you did the
> same with the current use case.
>
> Thanks,
> -Peter
>
> On Mon, Dec 9, 2024 at 8:56 AM Brian Goetz <brian.goetz at oracle.com> wrote:
>
> By "greater access", I think you mean "if they could be reflected
> on as a function, not just a thunk of behavior", yes? Then
> `createQuery(Customer::new)` would be able to reflect over the
> constructor reference and see "oh, it's a constructor for Customer".
>
> We've given a good deal of thought to this problem, but it's messy
> for a number of reasons, including erasure and limitations on the
> static type system. Does createQuery mean to limit to method
> references, rather than all lambdas? If so, then Supplier<T> is
> not a sharp enough type, and users could pass a Supplier that ends
> up causing a dynamic failure. If not, then it sounds like what
> you want is to reflect over the concrete shape of the supplier,
> which runs into erasure problems (among others -- what if you
> intended a Supplier<Foo>, but the lambda you passed instantiates a
> Supplier<SubtypeOfFoo> where the subtype is inaccessible to the
> framework.)
>
> So while it is possible to associate more metadata with method
> references than we currently do, and we've considered it, most of
> the obvious paths run into obvious roadblocks just a bit farther
> down the road.
>
>
>
>
> On 12/8/2024 8:52 PM, Peter Eastham wrote:
>> Hi Amber team,
>>
>> Earlier this week I was playing around with fluent API design,
>> and I believe Java could benefit from some improvements to the
>> usage of Method References, I'll keep this short with the
>> following example.
>>
>> Lets take the following JPA Criteria from section 6.3.3 of the
>> Specification, (Only for an example of what an API could begin
>> doing, not should do)
>> CriteriaQuery q = cb.createQuery(Customer.class);
>> Root customer = q.from(Customer.class);
>> Join order = customer.join(Customer_.orders, JoinType.LEFT);
>> q.where(cb.equal(customer.get(Customer_.status),
>> 1)).select(customer);
>>
>> If there was greater access to the Method Reference more implicit
>> information could be passed, reducing the noise,
>> CriteriaQuery q = CriteriaQuery.createQuery(Customer::new)
>> .leftJoin(Customer::orders)
>> .where(Object::equals, Customer::status, 1);
>> var result = q.select();
>>
>> Regarding what should be available, the resolved Class and Method
>> name at least, anything else I'd leave up to the feasibility of
>> the change.
>>
>> Thanks,
>> -Peter
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20241209/773f3bf9/attachment.htm>
More information about the amber-dev
mailing list