Two comments on the Constants API

Brian Goetz brian.goetz at oracle.com
Mon Jun 4 17:28:56 UTC 2018


> First -- and this is the easy one -- consider changing the package name
> to from `java.lang.invoke.constant` to `java.lang.constant`.

That's entirely reasonable; "JLC" was one of the candidates we kicked 
around, and there is nothing that makes JLIC intrinsically better than 
JLC.  We ended up with JLIC mostly because JLI has really become 
"JL-VM", and we thought that putting this in JLIC would make it clear to 
casual users that this is a low-level package for low-level users.  But, 
I would be happy with either choice.

> Second, please consider alternative approaches that don’t change the core
> classes that millions of Java developers use every day (e.g., `String`)
> by making them implement two new, obscure, low-level interfaces

Yeah, this has been a concern all along.  IMO, the main cost here is 
that, during the period between "WTF" and "Oh, I guess I can ignore it", 
there's cognitive friction.

In an earlier iteration, we had a type ("ConstantRef.OfSelf") which 
extended both ConstantRef and Constable, had default implementations of 
the two required methods (both of which just return `this`), and was 
implemented by the five special types (String, Integer, Long, Float, and 
Double.)  This got removed during the early review cycles of this API, 
on the theory of minimizing unnecessary new types, around the same time 
the Ref classes were renamed to Desc.  Such a mechanism could be brought 
back, probably with a different name.

That said, these five types _are_ special; they are fundamental to the 
JVM.  The challenge is, how we explain that in a non-distracting way, so 
people can get back on with their lives.

Of the two, I think it is considerably more important that String et al 
implement ConstantDesc than Constable; fortunately, I think this is also 
the less subtle and less arcane of the two to explain.

One of the big things pushing us towards String <: CD is that the 
varargs type `ConstantDesc...` shows up directly in the API in prominent 
places (e.g., the static argument list of indy/condy.) Strings and 
numbers are very commonly used as bootstrap arguments. (If it showed up 
only in a non-varargs input context, we could use overloadings more 
easily to work around special cases.)  Some bad options we encountered 
in the early days of this project, before the unifying type ConstantDesc 
was discovered:
  - We could type these API points as accepting `Object...` instead, and 
do a dynamic test to make sure that its either one of the valid, but 
this is more dynamic work and less type safety.  (This also creates a 
new API problem: since you can't safely overload methods with Object... 
varargs at all, this would affect a number of overloads with some fixed 
arguments and a varargs tail.)
  - We could have a wrapper type for string and friends that implements 
ConstantDesc; we actually tried this in an earlier version, and it was 
(a) error-prone (you forgot the wrapper _every time_), (b) made the code 
ugly, and (c) made the code less efficient.  I realize a/b are "just" 
pain borne by experts, but I think leaving this off of String and 
friends would have a pretty big impact on not only the intrinsics from 
JEP 303, but any other API that uses it (e.g., bytecode generation 
APIs.)  I would prefer to find a way to explain these types in a manner 
that gets people to "nothing to see here, move along" more quickly.

On the other hand, Constable rarely shows up in an API (yet); instead 
its used only reflectively by compilers / frameworks.  So if String and 
friends were not Constable, a compiler could add these dynamic checks in 
the shadow of the existing dynamic check for Constable-ness.

> I’m not sure what the best way to fix this might be.

So, here's two ways we could reduce this friction.

1.  Drop Constable (but not CD) off of String and friends (the cost of 
this is a manageable amount of additional spec+compiler complexity).  
Now the task reduces to explaining String <: CD. Some work on the spec 
of ConstantDesc, rewriting it from the perspective of "You probably got 
here because our mutual friend String gave you a coupon for a free 
symbolic reference", aiming to minimize the amount of time spent in that 
cognitive-friction zone between "WTF" and resumption of blissful 
ignorance, could help. And, CD is not _that_ hard to explain; it's 
something low-level code can use as the description for a low-level 
component of a classfile.

2.  Reintroduce some form of ConstantDesc.OfSelf, say NativeConstant 
(shed to be painted), which implements both CD and Constable and 
implements its methods.  This is trivial to do, and the Javadoc for this 
can be much simpler and friendlier than the Javadoc for CD, since it can 
appeal to "I'm just a convenient supertype for String and four special 
Number types."  The time spent in the cognitive-friction zone is even 
less.   But, its not perfect; while now String only directly extends 
NativeConstant, CD and Constable will still show up in the list of "all 
implemented interfaces." This could still be confusing.

Its possible that the downside of (2) could be further mitigated by 
Javadoc presentation changes (such as, in the "all implemented 
interfaces" list, instead of saying "A, B, C", it could say "A, B (via 
A), C", to make it clear what the inheritance path was.   If this list 
for String said "All implemented interfaces: Serializable, 
Comparable<String>, CharSequence, NativeConstant, ConstantDesc (via 
NativeConstant), Constable (via NativeConstant)", it would be clear that 
the weirdnesses are introduced by NC, and a short trip to the NC docs 
says "nothing to see here, move along."  In other words, maybe this is 
mostly a tooling problem.  (Or maybe this just is creating new problems.)

We've been in (2) before and I'm fine to go there again; the thing to 
most like about (2) is that it creates a type that explains the role of 
these five special types at a higher level.  (1) has the advantage of 
reducing the number of weird types that show up, but only gets us 
halfway back to blissful ignorance (there are only three numbers in 
computer science; 0, 1, and n.  We're in n now, this gets us back to 1, 
hence, halfway.)




More information about the amber-dev mailing list