On Oct 31, 2021, at 8:52 AM, Brian Goetz 
<brian.go...@oracle.com<mailto:brian.go...@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).



Reply via email to