> From: "John Rose" <john.r.r...@oracle.com>
> To: "Guy Steele" <guy.ste...@oracle.com>
> Cc: "Brian Goetz" <brian.go...@oracle.com>, "Tagir Valeev" 
> <amae...@gmail.com>,
> "Jim Laskey" <james.las...@oracle.com>, "amber-spec-experts"
> <amber-spec-experts@openjdk.java.net>
> Sent: Dimanche 31 Octobre 2021 01:18:38
> Subject: Re: Are templated string embedded expressions "method parameters" or
> "lambdas"?

> On Oct 30, 2021, at 3:22 PM, John Rose < [ mailto:john.r.r...@oracle.com |
> john.r.r...@oracle.com ] > wrote:

>> restrict the operand x (the receiver LHS of the S.T.)
>> to be a statically constant expression. If we do that,
>> then we can hand three things to a bootstrap method
>> that can do static validation:

>> 1. the (constant) receiver
>> 2. the string body (with holes marked)
>> 3. maybe the static types of the arguments (this is very natural for indy)

> Completing the design is pretty straightforward, but I might
> as well write out more of my work. Here’s *one possible* design
> in which the terminal “apply” operation is performed under
> the name “MethodHandle.invokeExact”.

> X."y…\{z…}" translates to an invokedynamic instruction

> The static arguments to the indy instruction are X (formed
> as a CONSTANT_Dynamic constant as necessary) and the
> string body containing y with hole indicators.

> Thus, the indy BSM gets the following:

> 1. a Lookup
> 2. a name (ignored)
> 3. a method-type (composed of the static types of z, returning R the 
> expression
> type)
> 4. X (via condy)
> 5. "y…" where the holes are appropriately marked

> It returns a CallSite, which is then used for all evaluations
> of the expression. Applications will use a ConstantCallSite.

> That is the mechanism. It does not say what is the logic of the BSM
> or the type R. That is where the language rules come in.

> The type of X must contain, directly or not, two or three methods,
> validate, apply, asMethodHandle. The methods are declared as abstracts
> using one or two API types. (Logically, they could also be left “hanging”
> outside of any interface as the magic methods Brian detests.)

One missing point is that it should be possible to do a static analysis of the 
code so asMethodHandle is a way to improve the performance, not to specify or 
change the semantics. 

> I will show one-interface and two-interface potential designs.

> interface ST_A<R,E> { // 1 type with 3 methods
> ST12 validate(Lookup, String, MethodType);
> <R> apply(E…);
> MethodHandle asMethodHandle();
> }

It's more an implementation details but if you have the method apply(E...), the 
compiler will generate a bridge method but the implementation will have not way 
to find it, 
you need also to provide the refied argument used as E. We had the same issue 
when generating the lambda proxy. 

> interface ST_B<R,E> { // 2 types with 1 or 2 methods
> Applier<R,E> validate(Lookup, String, MethodType);
> interface Applier<R,E> {
> <R> apply(Object… E);
> MethodHandle asMethodHandle();
> }
> //default R validateAndApply(Lookup, String, MethodType) { … }
> }

> interface ST_C<R> { // 1 type with 2 methods, plus MH
> MethodHandle validate(Lookup, String, MethodType);
> R validateAndInvoke(Lookup, String, MethodType, Object...);
> }
> // “apply” here is MethodHandle::invokeExact; asMethodHandle is a nop

> The language infers R as usual, as if the call were going through
> apply (A), validate then apply (B) or validateAndInvoke (C).
> But the BSM uses drives the same API points to obtain the
> needed MethodHandle, which is then installed in a CCS.

> Further variations: Have a static hook to produce, not a CCS
> but a general CS such as a MCS. Drop the Lookup argument
> from the APIs, because who wants that? You can add it later.

I think that having the Lookup argument is actually harmful, unlike the way we 
use BSM for lambdas, string concatenation or records where they are defined in 
the JDK, 
here the equivalent of the bootstrap method is implemented in library code. As 
a user, i don't want to pass a Lookup to my class to a library because i'm 
using a templated string, 
this is too much power. 

> The oddity here, as in existing prototypes, is that there are
> “two sets of books”, one for indy with its static bootstrap
> logic that produces a method handle, and one “for the
> police” which shows how all the types (including R) fit
> together.

> All of the above APIs allow implementations (subtypes) of the
> interface to supply different calling sequences for the eventual
> apply (or invoke). This is important, because a logger-oriented
> Applier wants to accept delayed evaluation lambdas if possible,
> while other simpler uses of the S.T. mechanism will be happy
> to get along with just the Object arguments of apply(Object…).

If you want to design a template policy for a logger you wan both to allow 
direct evaluation and delayed evaluation by providing two overloaded methods, 
like 
void log(TemplatedString template, Object... args) 
and 
void log(TemplatedString template, Supplier<?>... args) 

> One of the fine points of this design is whether and how
> to statically type the *hole arguments* and whether the
> static type of the receiver (x in x."…") can affect the
> subsequent static typing of the hole arguments. With
> a separate Applier type, the degrees of freedom in hole
> type checking are, maybe, a little easier to manage,
> but all of the API types above are malleable to some
> degree. Ultimately, I think we will be pushed to allow
> some amount of overloading on the “apply” method,

yes ! 

> if use cases demand static checking of argument
> lists. I’ve put in the “E” parameter above as a stop
> gap to allow (at least) the necessary distinction between
> Object and Supplier<Object> for distinct use cases.

> If we ever do “Varargs 2.0” (better varargs, with
> richer argument type patterns encoded into the VA
> receiver), that will naturally add value to the above
> APIs, if they can be retrofitted or replaced with VA2.0
> APIs situated on apply.

> That last one (ST_C) is nice and simple. Maybe that’s
> a good one to start with, maybe sans Lookup. The others
> can be layered on later on.

There is a forth variation, i will show it using an interface but it also work 
without it. 

You can merge validate and apply in one method and provide a way to transfer 
states between that method and asMethodHandle. 

If we have a method that conceptually returns a tuple 
interface Policy<P, R, E extends Throwable> { 
(R, Optional<MethodType -> MethodHandle>) validateAndApply(ConstantInfo info, 
P... args) throws E 
} 
with ConstantInfo containing the string with holes and the types of each holes. 

The return type return the value and Optionally a function that for a method 
type returns a method handle. 
The semantics is the following, the method validateAndApply is called, it 
validate the ConstantInfo, precompute a data structure from those constant 
arguments, applied the dynamic arguments of that data structure, return a value 
and optionally a lambda that for a method type and the data structure (captured 
by the lambda) returns a method handle. 

You have all the 3 steps, valide, apply and asMethodHandle into one method. If 
no lambda is provided, it works like String.valueOf() works, if a lambda is 
provided, the call to validateAndApply precompute the data structure used by 
the method handle and provide only the result of the first call all subsequent 
calls will use the method handle returned by the lambda. 

> A final word: If you said “that’s a curried function” to
> yourself at some point reading the above, you are not
> wrong.

Rémi 

Reply via email to