Are templated string embedded expressions "method parameters" or "lambdas"?
Remi Forax
forax at univ-mlv.fr
Sun Oct 31 15:38:53 UTC 2021
> From: "John Rose" <john.r.rose at oracle.com>
> To: "Guy Steele" <guy.steele at oracle.com>
> Cc: "Brian Goetz" <brian.goetz at oracle.com>, "Tagir Valeev" <amaembo at gmail.com>,
> "Jim Laskey" <james.laskey at oracle.com>, "amber-spec-experts"
> <amber-spec-experts at 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.rose at oracle.com |
> john.r.rose at 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
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20211031/a34553cc/attachment-0001.htm>
More information about the amber-spec-experts
mailing list