Are templated string embedded expressions "method parameters" or "lambdas"?

John Rose john.r.rose at oracle.com
Tue Nov 2 06:40:46 UTC 2021


On Oct 31, 2021, at 8:52 AM, Brian Goetz <brian.goetz at oracle.com<mailto:brian.goetz at oracle.com>> wrote:

I suggest that, until we roll out more of the machinery
we intend to roll out, such as type classes, that we
restrict the operand x (the receiver LHS of the S.T.)
to be a statically constant expression.

I think this is taking it way too far.

TL;DR:  I’m trying to avoid taking us too far in a different direction:  Into unproven mechanisms for bootstrapping static validation from dynamic method calls.  I’m trying to get us to use Java’s pre-existing strengths, which include a useful place for link-time validation, encoded via indy.


*If* the receiver is a statically constant expression, *then* it should be possible to get better type checking / translation.

I’m pointing out a different proposition:  *If* you need static validation of a string, *then* you need something constant about the receiver.  By constant I mean, very specifically, “fixed no later than just before the first execution of of the string template expression”.  The “something constant” might be the type (some subtype of a known “ST-Handler”), or it might be a constant value (e.g., a static final field value).

So, is it to be the type alone, or a constant receiver reference (with its own very specific type as well)?  I would prefer either choice over the current proposal which uses an unreliable MCS under the covers.

Or, a third choice (your plan of record, IIUC) is to attempt harvest the “something constant” from the *dynamic* receiver and arguments of the *temporally first* execution of the string template, and stash that somewhere, hoping it does not change.  This has the simplicity of having just one execution, but the new (and IMO objectionable) complexity of trying to recover the “something constant” needed for validation from a mishmash of dynamic information, filtered through a MCS, with unclear results when the “something constant” changes, and a murky optimization story for how to perform continual revalidation to detect dynamic receiver and arguments that turn out to be inconsistent with the chosen “something constant”.  That won’t optimize well, compared with a clear phase1/phase2 process where phase1 is run exactly once (via indy BSM) and phase2 is a method call with constant-folded configuration information from phase1.

(To be clear, this one-time validation hook I’m talking about is also what’s needed for self-configuration to feed something more efficient to the downstream computation; the Pattern.compile example shows this, and is typical of the cases I think are important.)

In a nutshell, one-time validation must be driven by one of  1. just the static type of the receiver, 2. the dynamic type of a link-time evaluated receiver, or 3. some dynamically testable aspect of a run-time evaluated receiver (which entails an N-time cost to maintain validation).

In a slightly larger nutshell, one-time validation/optimization/configuration requires one of the following, in the design space we are discussing:  1. validation driven by (only) the static type of the receiver, 2. validation driven by the dynamic type of the receiver, which is subject to an evaluate-once rule (via indy BSM, a link-time execution), 3. validation driven by the dynamic type, identity, or other state in the receiver, which may change, and therefore validation must be re-checked and potentially recomputed on every call, with various interfering effects from concurrency, dynamic invalidation testing, and at least potential deoptimization and revalidation.

Any of us could also spin a story for choice 1. which uses the type alone, of the receiver, rather than the type-plus-constant-value of the receiver.  I have illustrated the latter (choice 2.) because it seems more in line with *your* desires, to have everything pivot off of a dynamic dispatch (virtual method call) to a receiver.  But in order for that “everything” to include the “something constant” needed for validation, the receiver must be constant.  Until we get another source of “something constant” which would seem to be type class witnesses.

Choice 1 would be OK too, but it requires a clearer commitment to design in a validation call to a *static* method on the *static type* of the receiver (in lieu of a type-class witness, which would do a better job), followed by an run time (per-execution) *dynamic* call to whatever intermediate object was produced by the static method. Would you prefer that over choice 2?  I think it might have more of the “magic method” stink you are trying to avoid.  I’d be happier, on balance, with choice 1 (types only, magic static method on type for validation) even though it anticipates type classes in a clumsy way.  I have a write-up for that also, if you want to see my work.

Choice 3 needs a design pattern that I do not yet understand.  It interrupts the first execution of a dynamic-receiver computation, extracts some “static stuff” from that receiver (type? identity? field?), and computes a validated execution method for the rest of the computation, on the assumption that the “static stuff” will not change in subsequent calls.  This design pattern needs to be simple to reason about, needs appropriate bounds on when the “static stuff” will fail (and what happens then), and of course should optimize.  I suppose you think you have exhibited such a thing and that it is satisfactory, but I think it is much more complicated and unreliable than a two-step computation where a BSM takes some explicitly demarcated “static stuff” and its one-time product is applied N times to the remaining “dynamic stuff”.

But isn't constraining the receiver to be a static constant just more of the same sort of nannyism that you've been objecting to?

No, I don’t think so.  Someone reminding us of the laws of physics is not a nanny.  (In a moment I’ll try to figure out how you might think I’m nannying people away from doing something marginal but possible, and maybe that leads me somewhere.)

In terms of choice 1 or 2, starting from the requirement that there is a natural and physical need to call a method (validation) on the link-time configuration information of a S.T., there is then also a natural and physical need to marshal, statically at link time, the inputs to the validation.  If the receiver is to be a static input to the static validation computation, then of course it needs to be statically evaluable.  That’s not a nanny preventing me from doing something I can handle for myself, that’s a static checking rule preventing me from forming a physically impossible request (in the terms of design choice 2).

The design choice 3, your and Jim’s creation (and sharing features with wild ideas from Remi and other indy-nauts) is to manipulate a fully dynamic expression by extracting a quasi-static validation phase on the fly, and creating a cache which purports to safely and efficiently maintain the benefits of a quasi-static validation as long as possible, on a best-efforts basis.  With benchmark numbers that say, “see, as long as possible is pretty long!”

To the extent that I’m advocating nannyism, it’s of the form, “don’t lead users into design patterns that require magic caches and surprise re-validations”.  Am I wrong that that is in effect what is being proposed?  Or is there a “safe zone” for the use of your proposed S.T. semantics that (a) provably validates the string (and types) before first execution and (b) provably never re-validates, but runs full speed on the merits of the initial validation.

(Think Pattern.compile:  The S.T. body string disappears into the compiled regex, which is the only thing present for all subsequent executions.  I suppose the S.T. returns the compiled Pattern, perhaps in an incremental variant with additional information folded in from the expression holes.)

But my desire is to double down on Java’s pre-existing link-time evaluation phase, which is present for many kinds of expressions, including static member references and anything using indy.  That’s not nannying users, but rather it’s playing to Java’s existing strengths.  I want to nail the result of S.T. validation, exactly once, to the exact location that it occurs, which requires a static computation.  Requiring the target to be a static constant (in the absence of a type class witness) is simply a physical necessity to meet this need.  I don’t need a nanny to tell me not to flap my wings and fly; it’s just physics that keeps me grounded.

OK, so I would believe your nanny argument more if I believed there was a plausible alternative to an indy-based caching mechanism (sans MCS) for one-time validation.  Anything that uses a Mutable call site (or mutable anything) is going to be a hard sell for me, because it looks suspiciously hard to optimize and unreliable.  Am I wrong about that?  Please, show me how your MCS-based design pattern allows a variable receiver to co-exist with reliable one-time validation.  It would be best to distill it to its essentials, without the trappings of string templates and types interpolation holes; it really is a separate problem.

Or maybe (and this could be a disconnect on my part) you are proposing that people who use non-constant receivers (a) never get validation and (b) won’t care, and therefore (c) my objection to the presence of such don’t-care use cases is kind of nannyish.  After all if they don’t care about validation why should I take away their extreme variablility?  But I don’t think that’s the proposal.  I think you are offering variable receivers and quasi-constant validation.

Side note, applying to all designs we are talking about:  If someone doesn’t care about validation, or wants to control validation separately from the intrinsic semantics of the S.T. expression, then we all know there is a workaround:  Build a generic S.T. and pass it “manually” to a validation API point; then use the validated constant according to its desugared API; maybe it’s bound to a static variable for speed.  The sugar does not need to fully support every conceivable use case.

And because the sugar does not need to fully support every conceivable use case, I think it’s reasonable to make concessions to effective one-time static validation, including requiring a constant receiver (until type classes come on line).



-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20211102/1c8d7118/attachment-0001.htm>


More information about the amber-spec-experts mailing list