On Sep 25, 2019, at 10:04 AM, David Simms <[email protected]> wrote: > > - Remi: As a compiler strategy ? > - DanH: Allow user to hand write (e.g. pre-exisiting mig, i.e. Optional) > - Prefer compiler does the work > - Brian: sealing would provide trust to the VM > - John: sealed super-type that is empty (empty interface), a good way to > go ? > - Brian: possible > - Brian: which method we go with, must be driven primarily by primitives, > how to get to int. > - Something has got to give, fuzzing current type relationships. > "Digusting Hacks", "Special Behavior". > - Eclair and other food analogies continue, but we need to figure out > what we get for a given path first
I think the consensus is that for new value types (new eclairs) the JVM doesn’t have to do much and the translation strategy just emits standard JVM stuff. Sealing will let the JVM do optimizations at the JIT level. In the verifier and interpreter, sealed interfaces are probably not going to have special treatment so there is a small pre-JIT performance debit, compared with analogously “sealed” super-classes. (For superclasses, and not for interfaces, the verifier provides static type checking which the interpreter can exploit for a modest performance bump.) Old types migrated to value types are a different matter. Examples are ju.Optional and (maybe) jl.Integer. Consensus is that some kind of powerful low-level bridging mechanism from the JVM will go a long way toward making the new forms of these things compatible with old linkage requests (old client code). My hope is that bridges are the main contribution from the JVM. These two use cases correspond to two ends of an implementation spectrum for the eclair pattern. A thin-skinned eclair (Brian says paper-wrapped muffin) corresponds to an empty marker interface B and a value class V (the bulky muffin inside) that does all the work. The translation strategy emits linkage requests only against V. If the user writes method calls against B, they are lowered to calls on V (with an invisible cast, and possible NPE). The legitimacy of lowering calls on B to V methods is proven by the fact (which javac can observe) that B is sealed to V alone. So B can be physically empty. This is a nice thing for the JVM, since the JVM doesn’t like to see redundant inputs; they just cause useless binary compatibility hazards and expensive useless corner cases. The other end of the eclair spectrum would be a thick wrapper and tiny filling. This seens desirable when the box type has a previous career as a value base class like ju.Optional. Linkage requests from both legacy code and new code would apply to the box type B and not to the value type V. The value type V would be minimized down to some sort of record-like type. This is easiest to think about when V has a single field, as is the case with jl.Integer. In that case, Integer supply ad hoc box logic while the inner “int” would be the value type. Note that “int” has no methods; perhaps there is a way for B (the thick layer of box) to supply *all* methods, and V (the crunchy center) to supply *only* the state component. That also would be a sweet spot for the JVM, again because there is no duplication between the two classes B and V. I think Brian called this a “cannoli”. If it were savory it would be a tamale or dim sum bun with a fluffy outside and small bite at the center. I observe a spectrum here because it seems reasonable to allow users to “get their hands on” B in new code also, refactoring it from a blank interface to something more interesting. I can’t imagine why the user wouldn’t just put everything down on V in new code, but that’s just a temporary “can’t imagine”, not a true “would never happen”. Or maybe in a migration case the new crunchy kernel V has some natural new methods which interact with the old methods on B. If V is to be minimized by moving everything onto B, it follows that (as Brian and David observed) something has to give. Maybe B as an interface needs to get more power to define V’s state vector. Or maybe B needs to be a different kind of super. If B is an abstract super class that “abstractly” defines one field (or several fields) of state, then the concrete V can be totally blank, assuming there is a way for a value class to inherit state components. (Spinning the constructor is hard here, but maybe we can appeal to records for that.) So V is a record type all of whose components are inherited from B but concretized in V record-wise. Another option (besides interface or abstract class) is that the super of V is a template B of which V is really an instance B<‘value'>. This is not parametric polymorphism, but it is possibly a legitimate use of very ad hoc template polymorphism. Another way to think about using templates is that there are three types: B<_>, B<‘legacy’> and B<‘value’>. The job of the legacy class would be shared by the first two, while the new stuff would be expanded out from the third type. The types would all be named B (and B.class is the Object.getClass, always). The species would carry the distinctions between the three types. In this case, we might have jl.Integer<_> as a template that carries the legacy protocol (in all its glory), while Integer<‘legacy’> is an identity object in the old style (so you can still say “new Integer(5)”). Meanwhile the sibling subtype Integer<‘value’> is a true value type, the inside of the eclair. (The template parameters ‘legacy’ and ‘value’ might be booleans or enums, or even a pair of types. It doesn’t matter much. Note, however, that this is likely to be a use case for template arguments which are *not* types, just as C++ has many such use cases.) — John
