Re: Superclasses for inline classes
- Mail original - > De: "daniel smith" > À: "Remi Forax" > Cc: "valhalla-spec-experts" > Envoyé: Jeudi 13 Février 2020 15:04:41 > Objet: Re: Superclasses for inline classes >> On Feb 13, 2020, at 3:57 AM, fo...@univ-mlv.fr wrote: >> >> - Mail original - >>> De: "daniel smith" >>> À: "Remi Forax" >>> Cc: "valhalla-spec-experts" >>> Envoyé: Jeudi 13 Février 2020 00:49:34 >>> Objet: Re: Superclasses for inline classes >> >>>> On Feb 12, 2020, at 11:41 AM, Remi Forax wrote: >>>> >>>> a garbage class like java.util.Collections (with an 's' at the end) >>>> validate all >>>> the conditions but should not have an abstract constructor. >>> >>> Why not? If identity classes can extend it, and it has no >>> state/initialization, >>> why not inline classes too? >> >> Sorry to not be clear, because it's not a backward compatible change, >> the empty constructor becomes abstract. > > The JVM and language features (all 3 variations) are designed to ensure that > the > default constructor -> abstract constructor change is fully compatible. Can > you > illustrate what you think would not be compatible? What am saying is that garbage classes either are declared abstract or have a private constructor to avoid to be instantiated, the former will be eligible for (4) and not the later, so some garbage classes will become treated differently by the VM, will have their javadoc altered, the API seen by reflection changed, etc, even if it was not the intent of their authors. You're right that an abstract garbage class can be sub-classed, yes, using a private constructor that throws a NoSuchMethodError is a better design, but that not the point, the point is why retrofitting an existing class while it's not clear that it was the author intent. For me (4) is too clever with little benefit. We want to introduce a new kind of abstract class with a slightly different semantics that the one existing. Playing the retrofit game here doesn't worth the trouble. Rémi
Re: Superclasses for inline classes
> On Feb 13, 2020, at 3:57 AM, fo...@univ-mlv.fr wrote: > > - Mail original - >> De: "daniel smith" >> À: "Remi Forax" >> Cc: "valhalla-spec-experts" >> Envoyé: Jeudi 13 Février 2020 00:49:34 >> Objet: Re: Superclasses for inline classes > >>> On Feb 12, 2020, at 11:41 AM, Remi Forax wrote: >>> >>> a garbage class like java.util.Collections (with an 's' at the end) >>> validate all >>> the conditions but should not have an abstract constructor. >> >> Why not? If identity classes can extend it, and it has no >> state/initialization, >> why not inline classes too? > > Sorry to not be clear, because it's not a backward compatible change, > the empty constructor becomes abstract. The JVM and language features (all 3 variations) are designed to ensure that the default constructor -> abstract constructor change is fully compatible. Can you illustrate what you think would not be compatible? > There is a lot of classes like that in the wild, and given that the JLS > allows to call static methods on an instance, there are existing code that > are using the default constructor even if the author of the class never > wanted to allow such usage. If there is a class that allows unwanted subclasses, it will continue to allow unwanted subclasses. The way to change that is to make it 'final' or declare a 'private' constructor.
Re: Superclasses for inline classes
- Mail original - > De: "daniel smith" > À: "Remi Forax" > Cc: "valhalla-spec-experts" > Envoyé: Jeudi 13 Février 2020 00:49:34 > Objet: Re: Superclasses for inline classes >> On Feb 12, 2020, at 11:41 AM, Remi Forax wrote: >> >> a garbage class like java.util.Collections (with an 's' at the end) validate >> all >> the conditions but should not have an abstract constructor. > > Why not? If identity classes can extend it, and it has no > state/initialization, > why not inline classes too? Sorry to not be clear, because it's not a backward compatible change, the empty constructor becomes abstract. There is a lot of classes like that in the wild, and given that the JLS allows to call static methods on an instance, there are existing code that are using the default constructor even if the author of the class never wanted to allow such usage. Rémi
Re: Superclasses for inline classes
> On Feb 12, 2020, at 11:41 AM, Remi Forax wrote: > > a garbage class like java.util.Collections (with an 's' at the end) validate > all the conditions but should not have an abstract constructor. Why not? If identity classes can extend it, and it has no state/initialization, why not inline classes too? (In reality, this particular class has chosen to actively prevent subclassing and instantiation by declaring a private constructor; it wouldn't qualify): public class Collections { // Suppresses default constructor, ensuring non-instantiability. private Collections() { }
Re: Superclasses for inline classes
> On Feb 11, 2020, at 4:54 PM, Dan Smith wrote: > > Availability: Ideally, extending a suitable abstract class should be > frictionless for inline class authors. In (2) and (3), the authors are > blocked until the abstract class author opts in. In (4), the opt in is by > default, although there's still a requirement that the abstract class be > compiled with an appropriate source version. > > In practice, if we require an opt in, this will be an obscure, little-used > feature; or maybe it will become widespread, but we'll have introduced some > new standard boilerplate into the language. If we don't require an opt in, > inline classes will be free to extend most abstract classes automatically. Dan H raised a good point about availability on the call today: some tools like to instrument constructors, including empty/default ones. If we recompile most of the abstract classes in the world to make them instrumentation-hostile (as is the case if they have ACC_ABSTRACT methods), some of those tools are going to break, perhaps with no good workaround for the behavior they want. So there's a trade-off: widespread inline-friendly abstract classes will make inline class authors happy, but instrumentation tools sad. I don't have a sense of how serious this problem is, but it's something we should get a better handle on.
Re: Superclasses for inline classes
Hi all, sorry, was not available for the meeting today (i'm officially on vacation). I prefer (2) with a restriction like (1). I don't think users should be able to declare this kind of abstract class because you can not evolve them easily. I'm worried that if they become a tool available, people will over-use them (they are like a kind of abstract class with super-power, as a user it's like a shiny cookie), so in the end we will have to introduce inheritance of inline classes. This rule out (3). What (4) try to do is too clever IMO, a garbage class like java.util.Collections (with an 's' at the end) validate all the conditions but should not have an abstract constructor. It's too easy to create such kind of abstract class without knowing it. So for me, it's (2) + (1), at language level, a constructor can be declared abstract but it's restricted to class inside java.lang or java.base (enforced by the compiler and the VM). Rémi - Mail original - > De: "daniel smith" > À: "valhalla-spec-experts" > Envoyé: Mercredi 12 Février 2020 00:54:10 > Objet: Re: Superclasses for inline classes > So I think the JVM story is fairly settled; I've spun it off into another > thread, with a proposed spec. > > The language story is still uncertain. Here are four alternative designs, all > of > which are, I think, reasonable approaches to consider. Some discussion about > trade-offs is at the end. > > - > > Alternative 1: No language support > > The language requires all inline classes to extend Object (or perhaps > prohibits > the 'extends' clause). Abstract methods are available only as a > compiler > tool. > > Some special exceptions are necessary: > > - Somehow, class files for Object and Number must be generated with abstract > methods > > - Integer, Double, etc., are inline classes but are allowed to extend Number > > - > > Alternative 2: Abstract constructors in the language > > abstract class C { >public abstract C(); > } > > Like a method, a constructor can be declared 'abstract' and omit a body. > (Bikeshed: maybe the 'abstract' keyword is spelled differently, or left off > entirely.) > > These declarations can align closely with the JVM's rules for methods: > - The superclass must also have an abstract constructor > - No instance initializers or instance field initializers are allowed > - It's okay to overload the constructor, but 'abstract' only works on a no-arg > constructor > - (Perhaps) the class doesn't have to be abstract > - It's allowed (as a no-op) to invoke the constructor (new Object() or > super()) > > We'll need one further restriction that isn't checked by the JVM and isn't as > principled: no instance fields are allowed, even if they're > default-initialized. Otherwise, inline classes will have to search for private > fields to decide if extension is legal or not, breaking encapsulation. > > The abstract-ness of the constructor is part of the API—appears in javadoc, > changing it is incompatible. Having an empty or default constructor isn't the > same as having an abstract constructor. > > Inline classes can only extend classes with abstract constructors (and maybe > no > 'synchronized' instance methods). > > - > > Alternative 3: Inline-friendly property in the language > > inlineable abstract class C { > } > > The 'inlineable' property (bikeshed: how to spell this?) enforces some > constraints and authorizes children to be inline classes. > > Specific constraints: > - Can't have instance fields > - Can't declare a constructor or instance initializer > - Must extend an inlineable class > - Must be an abstract class or Object (maybe?) > - Must not have synchronized methods (probably?) > > An annotation spelling is an option, too, although that crosses a > line—there's a > precedent for annotations that prompt compiler errors, but not for annotations > that influence bytecode output. > > An inlineable class's bytecode has an abstract method. > > The 'inlineable' property is part of the API—appears in javadoc, changing it > is > incompatible. > > Inline classes can only extend inlineable classes. > > - > > Alternative 4: Infer no constructor > > Typically, for each class that lacks a constructor declaration, a default > constructor is provided that does some simple initialization. But in the > following circumstances, we claim there is no constructor at all: > > - Class doesn't declare a constructor or insta
Re: Superclasses for inline classes
> The language story is still uncertain. Here are four alternative designs, all > of which are, I think, reasonable approaches to consider. Some discussion > about trade-offs is at the end. Thanks for pulling this together into a nice menu, with chef’s recommendations. Let me add a few notes, which are mostly “soft” concerns. Overall, having fewer “feature X can’t work with Y” restrictions is better; alternative 1 (no language support) creates a “inline classes can’t extend abstract classes” interaction. WE know that there are at least a few abstract classes we want to have play nicely with inline classes: Number and Record immediately come to mind, but AbstractCollection is also a strong candidate. (AbstractList has fields, and therefore is a useful example because these fields are private and serve the implementation, which illustrates the dangers Dan is concerned about: that AbstractList starts out being like AbstractCollection, but then finds itself constrained in its evolution because it has inline subclasses.) I agree that giving this feature too-prominent billing (such as `inline-friendly abstract class`) is a bad trade. I also have no problem with the language inferring whether the superclass is suitable for extension by inline classes; this isn’t much different than requiring an accessible constructor. Some classes are suitable for extension by some other classes under some circumstances; as long as the rules are clear enough, and the compiler error messages are helpful when you cross the line, I don’t need a special opt in to say “It’s OK for this class to be extended by inline classes.” The problematic cases are all when abstract class C is extended by inline class V, _and C and V are in different maintenance domains_. If the two are in the same maintenance domain (package or module), then refactoring one may have consequences on the other, but the two can be co-refactored to avoid the problem. (This is just like refactoring an enum to be a class; there are pitfalls, but if the two are in the same maintenance domain, they can be more easily navigated.) So, the problematic case is: - A is a public abstract class in maintenance domain M, which is currently suitable for extension by inline classes - V is an inline class that extends A, in a separate maintenance domain N - Later, A wishes to refactor to have, say, private fields, but this would break V. This is surely a possible nasty surprise; the question is how often this is going to happen, and how much it would cost to prevent it from happening. Based on the numbers we got from Google’s code base (thanks!), it looks like ~3% of classes are public abstract classes, and many of those would meet the requirements to be extended by inline classes. But, less-than-3% is still a small number, and cross-maintenance-domain extension is rarer. (This is a mostly closed codebase; if this were a library, I suspect suitable exposed abstract classes would be even rarer, as all the style guidance suggests preferring exposing interfaces to abstract classes where possible.) So I think the chain of events that are required to cause regret (publicly exposed abstract class, cross maintenance-domain extension, and subsequent incompatible evolution) likely constitute a corner-of-a-corner case. This argues for something that minimizes the intrusion into the language. Why do we encourage people to expose interfaces and not abstract classes in APIs? Because interfaces are “more abstract”, and therefore exposing them is more constraining. So another lever at our disposal is: dial up the style-guide suggestions about not exposing abstract classes across maintenance domains. Finally, there’s some mitigation if the bad case comes to pass. Suppose we have: public abstract class Foo { /* accidentally inline friendly */ } and later you want to add fields to Foo and do: public abstract class Foo { int field; Foo(int f) { … } } private class Bar extends Foo { … } but can’t do so because some external inline class has extended it. You still have an option: public abstract class Foo { /* stays inline friendly */ } private abstract class SonOfFoo extends Foo { // all the non-inline-friendliness you want } private class Bar extends SonOfFoo { … } So, even though the author made a “mistake” by exposing the abstract class and therefore constraining themselves forever, the workaround isn’t so bad. Of the options, I’m still liking (4), backed up by an annotation like @FunctionalInterface that turns on extra type checking and documentation but serves only to capture design intent. > > - > > Alternative 1: No language support > > The language requires all inline classes to extend Object (or perhaps > prohibits the 'extends' clause). Abstract methods are available only > as a compiler tool. > > Some special exceptions
Re: Superclasses for inline classes
So I think the JVM story is fairly settled; I've spun it off into another thread, with a proposed spec. The language story is still uncertain. Here are four alternative designs, all of which are, I think, reasonable approaches to consider. Some discussion about trade-offs is at the end. - Alternative 1: No language support The language requires all inline classes to extend Object (or perhaps prohibits the 'extends' clause). Abstract methods are available only as a compiler tool. Some special exceptions are necessary: - Somehow, class files for Object and Number must be generated with abstract methods - Integer, Double, etc., are inline classes but are allowed to extend Number - Alternative 2: Abstract constructors in the language abstract class C { public abstract C(); } Like a method, a constructor can be declared 'abstract' and omit a body. (Bikeshed: maybe the 'abstract' keyword is spelled differently, or left off entirely.) These declarations can align closely with the JVM's rules for methods: - The superclass must also have an abstract constructor - No instance initializers or instance field initializers are allowed - It's okay to overload the constructor, but 'abstract' only works on a no-arg constructor - (Perhaps) the class doesn't have to be abstract - It's allowed (as a no-op) to invoke the constructor (new Object() or super()) We'll need one further restriction that isn't checked by the JVM and isn't as principled: no instance fields are allowed, even if they're default-initialized. Otherwise, inline classes will have to search for private fields to decide if extension is legal or not, breaking encapsulation. The abstract-ness of the constructor is part of the API—appears in javadoc, changing it is incompatible. Having an empty or default constructor isn't the same as having an abstract constructor. Inline classes can only extend classes with abstract constructors (and maybe no 'synchronized' instance methods). - Alternative 3: Inline-friendly property in the language inlineable abstract class C { } The 'inlineable' property (bikeshed: how to spell this?) enforces some constraints and authorizes children to be inline classes. Specific constraints: - Can't have instance fields - Can't declare a constructor or instance initializer - Must extend an inlineable class - Must be an abstract class or Object (maybe?) - Must not have synchronized methods (probably?) An annotation spelling is an option, too, although that crosses a line—there's a precedent for annotations that prompt compiler errors, but not for annotations that influence bytecode output. An inlineable class's bytecode has an abstract method. The 'inlineable' property is part of the API—appears in javadoc, changing it is incompatible. Inline classes can only extend inlineable classes. - Alternative 4: Infer no constructor Typically, for each class that lacks a constructor declaration, a default constructor is provided that does some simple initialization. But in the following circumstances, we claim there is no constructor at all: - Class doesn't declare a constructor or instance initializer - Class doesn't declare fields - Superclass has no constructor - Class is abstract or Object (maybe?) Again, blank private fields may not *need* initialization, but inline subclasses need to know that they exist, and the best way to communicate that is through the constructor. 'new Object()' (and perhaps 'new Foo()' if we drop the abstract class requirement) doesn't reference any constructor at all. In that case, a fresh instance is allocated without any code execution. For compatibility, this also applies to 'super()' (although we can discourage its use in these cases going forward). A class without a constructor has, in bytecode, an abstract method. The lack of a constructor is part of the API—appears in javadoc, changing it is incompatible. An annotation like @FunctionalInterface could help by checking that nothing has changed on recompilation. Inline classes can only extend classes that lack constructors (and maybe 'synchronized' instance methods). - Discussion Noise level: We don't want to make a big deal about this feature. People shouldn't think too much about it. (4) wins in this regard—no new syntax, just some hand-waving in the language model about what it actually means when you leave out your constructor. (2) introduces a subtle variation on constructor declarations, which can generally be overlooked. (3) is a neon invitation to treat these classes like a fundamentally new kind of entity. Compatibility: Adding or removing 'abstract' from an '' method is a binary incompatible change, so it's good if that doesn't happen accidentally. But it's hard to increase awareness of that commitment while minimizing noise level, so there are trade-offs. (2) and (3) both force authors to change something. (4) doesn't have that guardrail, and it's quite
Re: Superclasses for inline classes
I've been thinking about this, and gone around in circles a few times. The properties laid out here are understandable and easily checkable at both compile and run time. (I might add "no synchronization", or at least "no synchronized methods".) I agree that having some sort of class-level modifier gives this concept too much importance. And normally, I would take the position that a property like this would have to appear in the declaration somewhere, to capture the fact that conformance to these properties is not merely accidental and subject to change tomorrow. But, lately I've been questioning this. Reasons to have some sort of indicator of "I am an initialization-free class": - Allows author to capture design intent - Allows compiler to type-check conformance to requirements, based on design intent - Propagates design intent into documentation, where subclasses can see that this is safe to extend But it occurs to me that all of these are within the bounds of what _annotations_ are allowed to do; we did the same with `@FunctionalInterface` that, while not required, turns on additional compile-time type checking and documentation. I am currently thinking that we can adopt Dan's rules at the language level, and have an annotation @InitializationFree, which: (a) declares intent to conform to the contract of initialization-freedom, (b) unleashes compile-time type checking for same, and (c) causes a blurb to be emitted into the Javadoc along the lines of "This is an initialization-free class" -- but that the language specification works entirely in terms of structural properties. We can say that "inline classes may extend initialization-free inline classes", and be done. Regardless of compile-time type checking, we will likely want to do the analysis and write the result into the classfile (e.g., an InitializationFree class attribute), which can be seen as a necessary-but-not-sufficient signal for the VM to allow the class to be used as a super for an inline class. When we go to load a class with this attribute, we can verify that it actually conforms: - Fields: no fields - Constructors/initializers: no ctor/initializer - Class declaration: it is abstract, and its super is i-free - Synchronization: No methods have ACC_SYCHRONIZED When we go to load an inline class, we check the i-free bit on the superclass. None of these checks seem all that burdensome to the VM. It does mean that dusty abstract classes need to be recompiled before they can be bases for inline classes, which, on balance, seems OK. Language model An inline class may extend another class, as long as the superclass has the following properties: - It has no instance fields - It has no constructors - It has no instance initializers - It is abstract* or Object - It extends another class with these properties Subtype polymorphism works the same for superclasses as it does for superinterfaces. (*Remi points out that we could drop the 'abstract' restriction, meaning there may be identity instances of the superclass. Given the restriction on fields, though, I'm struggling to envision a use case; the consensus is that 'new Object()' is probably something we want to *stop* supporting.) Call a class that satisfies these constraints an "initialization-free class" (bikeshedding on this term is welcome!). Like an interface, its value set may include references to both inline class instances and identity class instances. We*do *not* want the initialization-free property to be expressed as a class modifier—this feature is too obscure to deserve that much prominence, encouraging every class author to consider one more degree of freedom; and we don't want every class to have to manually opt in. But we*do* need the initialization-free property to be part of the public information about the class. For example, the javadoc should say something like "this is an initialization-free class". Otherwise, it's impossible to tell the difference between, e.g., a class with no fields and a class with private fields. In the past, a Java class declaration that lacks a constructor always got a default constructor. In this model, however, an initialization-free class has no constructor at all. A 'super()' call directed at such a class is a no-op.
Re: Superclasses for inline classes
I kind of like your idea of a new flavor of constructor. If we drop the inference piece, it forces us to surface something in the language, but maybe it's subtle enough that, unlike a new flavor of class, it doesn't trip the "must include in Java 101" wire? While I have little faith in textbook authors to correctly make this call (based on having reviewed dozens of textbooks), yes, this seems a reasonable balance of the concerns.
Re: Superclasses for inline classes
A few of points in this thread emphasize differences between *declared* inline-friendly abstract classes and *implicitly* inline-friendly abstract classes. > On Dec 20, 2019, at 2:11 PM, John Rose wrote: > >> - it has no synchronized methods > > I think the natural way to phrase this is that the type itself does not > admit synchronization, either via a synchronized method or a > synchronized statement. Then it’s a type system property rather > than a structural property of methods. If the class is declared inline-friendly, then sure, this is a check we can make. If not, we can either: - Infer from the use of 'synchronized' that this is *not* an inline-friendly class - Call the class inline-friendly anyway, and let it blow up at run time A compile-time error is not an option. I lean towards the runtime error, because inferring a class property based on a random method modifier is too subtle. Put another way, if you want strong checking for 'synchronized' (and I think we can safely call this an option, not a necessity), you also probably want some sort of explicit opt-in to inline friendliness. >> Object, and all interfaces, would be inline-friendly (we can adjust the >> declaration of Object to meet this requirement); the compiler would >> structurally recognize abstract classes as inline-friendly and set the bits >> in the classfile. > > I like this, as long as “structurally” includes some explicit signal either > in the > source code or (at least) in superclass (from which a new default would be > silently inferred). IMO there’s it’s hard to see a case for “promoting” > apparently-empty constructors (like C(){}) into declaratively-empty ones. > They would become invisibly-non-empty via action at a distance in supers > and in field definitions. Hmm, action at a distance is inevitable in the "just infer it" model. Somebody decides to add an innocent-seeming private field, and they've broken binary compatibility with their subclasses. Are we being too clever? We need the class to give us a permanent guarantee about the API; but at the same time, we're hoping the author doesn't have to notice that they're making this promise. That may not be workable. I kind of like your idea of a new flavor of constructor. If we drop the inference piece, it forces us to surface something in the language, but maybe it's subtle enough that, unlike a new flavor of class, it doesn't trip the "must include in Java 101" wire?
Re: Superclasses for inline classes
On Dec 20, 2019, at 7:59 AM, Brian Goetz wrote: > > Stepping back, the thing that frightened us away from this was the > combination of (a) not wanting to have a modifier on abstract classes to > indicate inline-friendly, Perhaps that’s made better by inverting the sense of the “bit” (perhaps modifier), so that inline-hostile classes have the burden of marking themselves, and inheritance can help with that (but only if the bit is inverted). But inline-friendly types still need to contrive declaratively-empty constructors, so it looks like there’s a lot of recompilation in our future. > and (b) worrying that it was a lot of (brittle) work to structurally detect > inline-friendly abstract classes. Dan has cut this knot by tying it to the > presence of a declaratively-empty constructor, which changes the story a lot. This is a specific case of making abstract classes more like interfaces. If you look carefully at interfaces, you can see that their flexibility derives, in large part, from the lack of constructors. (This in turn implies the lack of instance fields, but in a secondary way: You can’t control such fields fully without a constructor.) Having declaratively empty constructors in classes (abstract or not!) opens for them some of the same paths that interfaces enjoy. This reasoning can go the other way, too, to make interfaces more like abstract classes. As I’ve pointed out before, interfaces could be given explicit declaratively-empty constructors, which in turn could be given less-public access than the interfaces themselves. This would provide the same level of subclass control for interfaces as for abstract classes, with an effect close to “sealed interfaces”, from a class-like primitive (access control on constructors). In particular an interface could be sealed to its nest mates by marking its declaratively-empty constructor as private (and so on). I’m not pointing this out to say we should done sealing differently; I love the way sealing turned out. But it’s important to be aware of some underlying “class physics” at play here with both interfaces and abstracts. As a VM guy I tend to see the “physics" this way as logical design constraints that bubble up from the VM, instead of starting with a desired psychology and working down through the chemistry (if you get my drift). Over time I see interfaces becoming more like abstract classes (notably with default methods), and abstract classes returning the favor by growing declaratively-empty constructors. This is not an accident. I’m convinced that as we continue to pay attention to the “physics”, we will be better informed in our treatment of other aspects of types, including instance fields and identity. > (I remain unconvinced that instance fields in inline-friendly abstract > classes could possibly be in balance, cost-benefit-wise, and > super-unconvinced about inlines extending non-abstract classes.) My concern here is to point out the logical possibility of such things, not to advocate for them now. Treating fields and non-abstract supers as corollaries, rather than than axioms, makes me more certain we have grasped the physical essentials of the problem. This is a valuable design heuristic: We know that if we can say “no” to features while still understanding how they could fit in the future, we have arrived at a more factored, more desirable design. This is why I’m talking now, hypothetically, about fields and concretes. (Which is more important, physics or psychology? Neither and both, I suppose, but physics has this privilege: If you build on an inconsistent or gratuitously complex logical foundation, your user experience will never ever be as smooth as it could be.) > As John says, there are three potential states for an abstract type: > always-identity (true of traditional abstract classes), always-inline, and > identity-agnostic. (A possibly way to capture these is by leaning on our new > friends IdentityObject and InlineObject; an always-inline abstract type > implements InlineObject, and always-identity abstract type implements > IdentityObject, and an agnostic one implements neither.) It is also possible > we might prune away the always-inline flavor of abstract types, leaving us > with two: inline-friendly or identity-locked. I think the always-inline flavor of abstract *classes* can be replaced, for most use cases, with interfaces (with sealing + default methods - toString), and later on with templates (as long as the polymorphism is parametric). Many uses of an always-inline flavor of *interfaces* can be replaced by a sealed interface, where all the permits are inlines. Dan and Remi have pointed out some places where we might be confronted with a demand for more, which means we should keep our eyes open. I’m very happy to bid less, for now, and perhaps forever. > From a JLS perspective, we could say an abstract type is inline-friendly iff: > - its su
Re: Superclasses for inline classes
Stepping back, the thing that frightened us away from this was the combination of (a) not wanting to have a modifier on abstract classes to indicate inline-friendly, and (b) worrying that it was a lot of (brittle) work to structurally detect inline-friendly abstract classes. Dan has cut this knot by tying it to the presence of a declaratively-empty constructor, which changes the story a lot. (I remain unconvinced that instance fields in inline-friendly abstract classes could possibly be in balance, cost-benefit-wise, and super-unconvinced about inlines extending non-abstract classes.) As John says, there are three potential states for an abstract type: always-identity (true of traditional abstract classes), always-inline, and identity-agnostic. (A possibly way to capture these is by leaning on our new friends IdentityObject and InlineObject; an always-inline abstract type implements InlineObject, and always-identity abstract type implements IdentityObject, and an agnostic one implements neither.) It is also possible we might prune away the always-inline flavor of abstract types, leaving us with two: inline-friendly or identity-locked. From a JLS perspective, we could say an abstract type is inline-friendly iff: - its supertypes are all inline-friendly - it has a declaratively empty constructor - it has no synchronized methods - it has no instance fields - it has no instance initializers Object, and all interfaces, would be inline-friendly (we can adjust the declaration of Object to meet this requirement); the compiler would structurally recognize abstract classes as inline-friendly and set the bits in the classfile. In migrating Integer and friends to be inline-friendly (or inline-locked) abstract classes, all we do is push the fields and instance method implementations down into the primitive classes, which is fine because these classes are final. (Making Integer an abstract class vs an interface means that int inherits the stupid statics on Integer, like the terminally confusing getInteger(String). Maybe some further deprecation of static inheritance is warranted here.) If we say "instance fields is a can of worms" (which I think we should), we get a further simplification: we don't need to deal with the combination of both declaratively-empty constructors and traditional constructors; either you have real constructors (identity-locked) or empty ones (inline-locked/friendly), and in the case of an inline-friendly being extended by an identity class, the super constructor call is seen as a no-op. So, (3) allow the constructor with no arguments to be declared “abstract” with no body. Amend the JVM rules to allow this, and to check upward to the super for the same condition. During static checking of source, treat such a constructor as empty,*and* as forbidding non-static initializers. I think that gets what we need in a much clearer manner. The remaining bikeshed this leaves us is how to mark the constructor (abstract seems a good strawman), and whether to pull on the {Inline,Identity}Object levers. On 12/19/2019 9:12 PM, John Rose wrote: On Dec 18, 2019, at 3:57 PM, Dan Smith wrote: [Expanding on and summarizing discussion about abstract superclasses from today's meeting.] - Motivation There are some strong incentives for us to support inline classes that have superclasses other than Object. Briefly, these include: - Identity -> inline migration candidates (notably java.lang.Integer) often extend abstract classes - A common refactoring may be to extend an existing class with a (possibly private) inline implementation - Abstract classes are more expressive than interfaces - If we compile Foo.ref to an abstract class, we can better represent the full API of an inline class using an abstract class I’m glad we are cracking open this can of worms; I’ve always thought that interfaces as inline supers were good enough but not necessarily the whole story. (At the risk of instilling more terror, I’ll say that I think that an abstract super to an inline could contribute non-static fields, in a way that is meaningful, useful, and efficient. The initialization of such inherited fields would of course use withfield and would require special rules to allow the initialization to occur in the subclass constructor/factory. I suppose this is a huge feature, as Dan says later. A similar effect will be available from templates, with less special pleading.) (Does it make sense to allow an abstract class to *also* be inline? Maybe, although there is a serious question about its default value. If a type is abstract its default value is probably required to be null.) A useful organizing concept for abstract supers, relative to inlines, is a pair of bits, not both true, “always-inlined” and “never-inlined”. Object and interfaces have neither mark by default. The super of an identity class cannot be “always-inlined" and the super of an inline c
Re: Superclasses for inline classes
On Dec 18, 2019, at 3:57 PM, Dan Smith wrote: > > [Expanding on and summarizing discussion about abstract superclasses from > today's meeting.] > > - > Motivation > > There are some strong incentives for us to support inline classes that have > superclasses other than Object. Briefly, these include: > > - Identity -> inline migration candidates (notably java.lang.Integer) often > extend abstract classes > - A common refactoring may be to extend an existing class with a (possibly > private) inline implementation > - Abstract classes are more expressive than interfaces > - If we compile Foo.ref to an abstract class, we can better represent the > full API of an inline class using an abstract class I’m glad we are cracking open this can of worms; I’ve always thought that interfaces as inline supers were good enough but not necessarily the whole story. (At the risk of instilling more terror, I’ll say that I think that an abstract super to an inline could contribute non-static fields, in a way that is meaningful, useful, and efficient. The initialization of such inherited fields would of course use withfield and would require special rules to allow the initialization to occur in the subclass constructor/factory. I suppose this is a huge feature, as Dan says later. A similar effect will be available from templates, with less special pleading.) (Does it make sense to allow an abstract class to *also* be inline? Maybe, although there is a serious question about its default value. If a type is abstract its default value is probably required to be null.) A useful organizing concept for abstract supers, relative to inlines, is a pair of bits, not both true, “always-inlined” and “never-inlined”. Object and interfaces have neither mark by default. The super of an identity class cannot be “always-inlined" and the super of an inline class cannot be “never-inlined”. Or, an identity (resp. inline) class has the “always-inlined” (resp. “never-inlined”) bit set. And for every T <: U in the class hierarchy, if T is always-inlined, then U must not be never-inlined, and vice versa. Thus if U is marked then every T <: U is forbidden to have the opposite mark. Or, even more simply, both bits are deemed to inherit down to all subtypes, and no type may contain both marks. I don’t know how to derive those bits from surface syntax. A marker interface for each is a first cut: AlwaysInlined, NeverInlined. Marker interfaces are kind of smelly. These particular ones work a little better than their complements (InlineFriendly, IdentityFriendly) because they exclude options rather than include them. (Could a *non-abstract* inline be a super of another inline? No, I’d like to draw the line there, because that leads to paradoxes with flattening, or else makes the super non-flattenable in most uses, or violates a substitutability rule.) > To be clear, much of this has to do with migration, and I subscribe to a > fairly expansive view of how much we should permit and encourage migration. I > think most every project in the world has at least a few opportunities to use > inline classes. Our design should limit the friction necessary (e.g., > disruptive redesigns of type hierarchies) to integrate inline classes with > the existing body of code. You have a point. Migration is not a task but a way of life? > We've considered, as an alternative, supporting transparent migration of > existing classes to interfaces. But this raises many difficult issues > surrounding source, binary, and behavioral compatibility. It would be nice > not to have to tackle those issues, nor introduce a lot of caveats into the > class -> interface migration story. As I said earlier, for value types we have this recurring need to bend interfaces to be more like abstract classes, or else allow abstract classes to become more like interfaces. > - > Constraints > > Inline class instantiation is is fundamentally different from identity class > instantiation. While the language seeks to smooth over these differences, > under the hood all inline objects come from 'defaultvalue' and 'withfield' > invocations. There is no opportunity in these bytecodes for a superclass to > execute initialization code. In the case we are discussing, the interface-like trick that abstract classes need to learn is to have (declaratively) empty constructors. I think that if a class (abstract or not) has a non-empty constructor, it must also be given the “never-inline” mark. (This is one reason that mark isn’t simply a marker interface.) In this way (or some equivalent) a class with a non-empty constructor will never attempt to be the super of an inline. > (Could we redesign the construction model to properly delegate to a > superclass? Sure, but that's a huge new feature that probably isn't justified > by the use cases.) Probably not. Unless folks demand to factor fields as well as behaviors into abstract supers of inlines. > As a resu
Re: Superclasses for inline classes
On Dec 18, 2019, at 3:57 PM, Dan Smith wrote: > > abstract superclasses A quick observation: The Panama Vector API uses interfaces to abstract from specific vector types, and those specific vector types must be made inline to enable full optimization. For example: Float128Vector <: FloatVector <: Vector <: Vector In this scheme we either need to seal FloatVector to Float128Vector and its siblings, or else we need to do something with abstract classes. There may be incremental benefits to making FloatVector be an abstract class, such as being able to define and document toString, equals, and hashCode. If in addition we could define FloatVector as an abstract *inline* class, we might benefit from the effect of FloatVector being non-nullable. The top-level interface Vector would still be nullable. The plan of record is to make every vector type be either an interface or an inline, and then move to specialized interfaces and inlines when templates become available. But Vector is one place where abstract classes are worth a serious look. — John
Re: Superclasses for inline classes
Yes, it is totally on the table that the language model doesn't bother to introduce an implicit class or interface above the inline class, and instead we're talking about two types derived from a single class declaration. No matter what choice we make there, there's a separate question of whether inline classes can extend other classes, and I'm arguing that there are some good reasons that they should. (There's also the question of how a 'ref' type gets compiled, nevermind what the language model says, and whether an abstract class would be a useful tool for that purpose.) > On Dec 19, 2019, at 2:51 AM, Remi Forax wrote: > > It occurs to me that we may have jump into the conclusion that we should use > inheritance/implementation a little to fast. > At least, i think it worth exploring another complementary option. > > In Java, you have several ways to have subtyping, > saying a FooInline is a subclass FooInline.ref is one way, > but using wildcard is another one, if Foo can be a template classes with > Foo the inline. > > It will not solve the retrofitting of java.lang.Number so it's a > complementary approach but it can solve the retrofitting of java.lang.Integer > in a better way than to use subclasses. > > Let say java.lang.Integer is a template class, but not a template class over > a type like a specialized generics are but a template class over the inline > bit. > In that case java.lang.Integer can have two specializations, one is an > indirect type, the result of new Integer(8), and one is an inline type the > result of Integer.valueOf(8). > > The nice properties of this organization are: > - object.getClass() will return Integer.class for both specializations (it's > not the same species but it's the same class). > - java.lang.Integer can still be final (because final means no subclass but > not no subtype). > > Rémi > > > >
Re: Superclasses for inline classes
It occurs to me that we may have jump into the conclusion that we should use inheritance/implementation a little to fast. At least, i think it worth exploring another complementary option. In Java, you have several ways to have subtyping, saying a FooInline is a subclass FooInline.ref is one way, but using wildcard is another one, if Foo can be a template classes with Foo the inline. It will not solve the retrofitting of java.lang.Number so it's a complementary approach but it can solve the retrofitting of java.lang.Integer in a better way than to use subclasses. Let say java.lang.Integer is a template class, but not a template class over a type like a specialized generics are but a template class over the inline bit. In that case java.lang.Integer can have two specializations, one is an indirect type, the result of new Integer(8), and one is an inline type the result of Integer.valueOf(8). The nice properties of this organization are: - object.getClass() will return Integer.class for both specializations (it's not the same species but it's the same class). - java.lang.Integer can still be final (because final means no subclass but not no subtype). Rémi