Re: RefObject and ValObject
On Apr 15, 2019, at 1:37 PM, fo...@univ-mlv.fr wrote: > > It's not bikeshedding, hence the "if you prefer ...", > it's trying to explain that it's a typing issue and not a runtime class issue. Sure. The "if you prefer" is a subtle hint that bikeshedding is not intended. To avoid having your audience think that bikeshedding has begun, a *non-subtle* hint is even more helpful, which is why I like to use intentionally ugly pseudo-syntax (like __NWC_BLOB__, in another thread) during such discussions. > - Mail original - >> De: "John Rose" >> À: "Remi Forax" , "Maurizio Cimadamore" >> >> Cc: "Brian Goetz" , "valhalla-spec-experts" >> >> Envoyé: Lundi 15 Avril 2019 20:13:50 >> Objet: Re: RefObject and ValObject > >> Avoid the Bikeshed! Make sure that temporary or provisional >> syntaxes look __Different from permanent ones. >>
Re: RefObject and ValObject
On 15/04/2019 21:54, fo...@univ-mlv.fr wrote: m(List list ) { ... } ... m(List.of(new Foo(), new Bar())); Slightly incorrect sir :-) This is effectively equivalent to the example I wrote earlier List list = List.of(...) The expected type will force the equality constraint and will drive inference home. Otherwise it would not even work today in cases when you pass e.g. Integer and String to List.of, as their common supertype is something sharper than Object. This stuff used indeed to fail pre-Java 8, but I think we cured most of the issues. The remaining ones are when inference eagerly resolves variables w/o looking at the target, because a target is not there, as for 'var', or because the expression is in a receiver position, as in: List list2 = List.of(new Foo(), new Bar()).subList(0, 42); Now, this will fail, but I believe we're in the corner^2 territory? Maurizio
Re: RefObject and ValObject
Most of what i wrote in the L10/L20 memo is accurate :) > On Apr 15, 2019, at 5:32 PM, fo...@univ-mlv.fr wrote: > > > > De: "Brian Goetz" > À: "Remi Forax" > Cc: "valhalla-spec-experts" > Envoyé: Lundi 15 Avril 2019 23:23:57 > Objet: Re: RefObject and ValObject > V <: V? by value set inclusion; V? is the type obtained by adjoining `null` > to the value set of V. > > Looking for.a better name for this. :”Nullable value types” is a terrible > name, so I don’t want to say that. (Too confusing with null-default value > types.). They could properly be define as “null-adjoined value types”, but > that’s not helpful if you don’t know what an adjunction is. Similarly for > “nullable projection”. > > But the basic idea is that V? is the denotation of the union type of V | > Null. > > > Ok, i think i got slightly confused about the difference between RefObject > and ValueRef? > > Rémi > > >
Re: RefObject and ValObject
> De: "Brian Goetz" > À: "Remi Forax" > Cc: "valhalla-spec-experts" > Envoyé: Lundi 15 Avril 2019 23:23:57 > Objet: Re: RefObject and ValObject >> V <: V? by value set inclusion; V? is the type obtained by adjoining `null` >> to >> the value set of V. > Looking for.a better name for this. :”Nullable value types” is a terrible > name, > so I don’t want to say that. (Too confusing with null-default value types.). > They could properly be define as “null-adjoined value types”, but that’s not > helpful if you don’t know what an adjunction is. Similarly for “nullable > projection”. > But the basic idea is that V? is the denotation of the union type of V | Null. Ok, i think i got slightly confused about the difference between RefObject and ValueRef? Rémi
Re: RefObject and ValObject
The word "reference" is available and fits the bill. "nullable reference type" > On Apr 15, 2019, at 2:23 PM, Brian Goetz wrote: > >> V <: V? by value set inclusion; V? is the type obtained by adjoining `null` >> to the value set of V. > > Looking for.a better name for this. :”Nullable value types” is a terrible > name, so I don’t want to say that. (Too confusing with null-default value > types.). They could properly be define as “null-adjoined value types”, but > that’s not helpful if you don’t know what an adjunction is. Similarly for > “nullable projection”. > > But the basic idea is that V? is the denotation of the union type of V | > Null. >
Re: RefObject and ValObject
> V <: V? by value set inclusion; V? is the type obtained by adjoining `null` > to the value set of V. Looking for.a better name for this. :”Nullable value types” is a terrible name, so I don’t want to say that. (Too confusing with null-default value types.). They could properly be define as “null-adjoined value types”, but that’s not helpful if you don’t know what an adjunction is. Similarly for “nullable projection”. But the basic idea is that V? is the denotation of the union type of V | Null.
Re: RefObject and ValObject
In the document on “Towards a plan for L10 / L20” I tried to answer these, but I got it slightly wrong. I said: V <: V? <: ValObject <: Object But really that should be V <: V? <: ValObject? <: Object V <: ValObject <: Object V <: V? by value set inclusion; V? is the type obtained by adjoining `null` to the value set of V. I am leaning towards saying that `RefObject?` and `Object?` are not sensible things to write, because they are equal to `RefObject` and `Object`. (That’s separately from `T?`, which always makes sense, but sometimes just means “T”.). No flavor of Point is a subtype of any flavor of RefObject; ValObject and RefObject are disjoint. > On Apr 15, 2019, at 5:10 PM, fo...@univ-mlv.fr wrote: > > > > De: "Brian Goetz" > À: "Remi Forax" > Cc: "Maurizio Cimadamore" , > "valhalla-spec-experts" > Envoyé: Lundi 15 Avril 2019 22:46:08 > Objet: Re: RefObject and ValObject > yes ! > all generics will suddenly accept value types. > > > Yes, this is by design. If you can’t have an ArrayList of Point, that would > be terrible. Of course, until we have specialization (later in the story), > these will be erased, and restricted to the nullable projection. > > Does it means that Point? is a subtype of RefObject ? > > Note: the second question mark in this sentence is because it's a question. > > Rémi >
Re: RefObject and ValObject
> De: "Brian Goetz" > À: "Remi Forax" > Cc: "Maurizio Cimadamore" , > "valhalla-spec-experts" > Envoyé: Lundi 15 Avril 2019 22:46:08 > Objet: Re: RefObject and ValObject >> yes ! >> all generics will suddenly accept value types. > Yes, this is by design. If you can’t have an ArrayList of Point, that would be > terrible. Of course, until we have specialization (later in the story), these > will be erased, and restricted to the nullable projection. Does it means that Point? is a subtype of RefObject ? Note: the second question mark in this sentence is because it's a question. Rémi
Re: RefObject and ValObject
> De: "Maurizio Cimadamore" > À: "Brian Goetz" , "Remi Forax" > Cc: "valhalla-spec-experts" > Envoyé: Lundi 15 Avril 2019 22:53:49 > Objet: Re: RefObject and ValObject > I think we should be ok inference-wise, except of course for edge cases (like > vars) which exposes the guts of the inference engine. > In normal cases like: > List list = List.of(new Foo(), new Bar()) > the equality constraint on the LHS should force a solution which overrides the > new 'RefObject' bound that comes in from the RHS. > (that is, you have T = Object, then T :> Foo and T :> Bar, so T = Object > 'wins', > even if less precise) > Maurizio yes, i think you're right, Rémi > On 15/04/2019 21:39, Brian Goetz wrote: >>> 1) any codes that has inference >>> var list = List.of(new Foo(), new Bar()); >>> will be inferred as List instead of List, so calling a >>> method >>> that takes a List will not compile anymore. >> Not to dismiss the “what about inference” issue, but any code that combines >> `var` with `List,of()` should not be surprised at the result of inference …. >> But, “what about inference” noted. >>> 2) any code that consider Object as special class may stop working, dynamic >>> proxies that have a special case for Object, any code that reflect >>> recursively >>> on the hierarchy to find all the methods, any code that remove getClass() >>> from >>> the getter list, etc. >> Absorbing value types is going to require some changes on the part of most >> frameworks to get best results. This one seems in that category? >> Again, not to dismiss, keep them coming, but so far this isn’t scaring me >> away >> from having the object model that makes most sense.
Re: RefObject and ValObject
I think we should be ok inference-wise, except of course for edge cases (like vars) which exposes the guts of the inference engine. In normal cases like: List list = List.of(new Foo(), new Bar()) the equality constraint on the LHS should force a solution which overrides the new 'RefObject' bound that comes in from the RHS. (that is, you have T = Object, then T :> Foo and T :> Bar, so T = Object 'wins', even if less precise) Maurizio On 15/04/2019 21:39, Brian Goetz wrote: 1) any codes that has inference var list = List.of(new Foo(), new Bar()); will be inferred as List instead of List, so calling a method that takes a List will not compile anymore. Not to dismiss the “what about inference” issue, but any code that combines `var` with `List,of()` should not be surprised at the result of inference …. But, “what about inference” noted. 2) any code that consider Object as special class may stop working, dynamic proxies that have a special case for Object, any code that reflect recursively on the hierarchy to find all the methods, any code that remove getClass() from the getter list, etc. Absorbing value types is going to require some changes on the part of most frameworks to get best results. This one seems in that category? Again, not to dismiss, keep them coming, but so far this isn’t scaring me away from having the object model that makes most sense.
Re: RefObject and ValObject
> De: "Brian Goetz" > À: "Remi Forax" > Cc: "Maurizio Cimadamore" , > "valhalla-spec-experts" > Envoyé: Lundi 15 Avril 2019 22:39:43 > Objet: Re: RefObject and ValObject >> 1) any codes that has inference >> var list = List.of(new Foo(), new Bar()); >> will be inferred as List instead of List, so calling a >> method >> that takes a List will not compile anymore. > Not to dismiss the “what about inference” issue, but any code that combines > `var` with `List,of()` should not be surprised at the result of inference …. > But, “what about inference” noted. you don't need var, var represent the inference value of an expression, instead of having two lines, you can always write m(List list ) { ... } ... m(List.of(new Foo(), new Bar())); >> 2) any code that consider Object as special class may stop working, dynamic >> proxies that have a special case for Object, any code that reflect >> recursively >> on the hierarchy to find all the methods, any code that remove getClass() >> from >> the getter list, etc. > Absorbing value types is going to require some changes on the part of most > frameworks to get best results. This one seems in that category? > Again, not to dismiss, keep them coming, but so far this isn’t scaring me away > from having the object model that makes most sense. but this compatibility issues have nothing to do with value types pre se, it's because of the introduction of RefObject, and currently all other compatibility issues can be solved at use site (i believe), here you are adding compatibility issue that can only be solved at declaration site, that's a big change because now as a user you have to wait until all your dependencies have been updated before using value type. Rémi
Re: RefObject and ValObject
> De: "Maurizio Cimadamore" > À: "Brian Goetz" , "Remi Forax" > Cc: "valhalla-spec-experts" > Envoyé: Lundi 15 Avril 2019 22:23:25 > Objet: Re: RefObject and ValObject > Maybe (Remi correct me if I'm wrong), the problem Remi was referring to is > that > we also have existing generic declarations like which, in > the new world, will mean either VALUE or REFERENCE. I think this is a > consequence of the choice (1) I described in my email - e.g. reinterpret > Object > in type position as TOP_TYPE. > Maurizio yes ! all generics will suddenly accept value types. Rémi > On 15/04/2019 19:00, Brian Goetz wrote: >>> It's not a minor change, and all code that uses a type parameter that have >>> Object as bound will become ambiguous. >> I don’t think so. You can’t say >> new T() >> when T is bounded at Object (or anything, for that matter.). >> What ambiguity are you afraid of here?
Re: RefObject and ValObject
> > 1) any codes that has inference >var list = List.of(new Foo(), new Bar()); > will be inferred as List instead of List, so calling a > method that takes a List will not compile anymore. Not to dismiss the “what about inference” issue, but any code that combines `var` with `List,of()` should not be surprised at the result of inference …. But, “what about inference” noted. > 2) any code that consider Object as special class may stop working, dynamic > proxies that have a special case for Object, any code that reflect > recursively on the hierarchy to find all the methods, any code that remove > getClass() from the getter list, etc. Absorbing value types is going to require some changes on the part of most frameworks to get best results. This one seems in that category? Again, not to dismiss, keep them coming, but so far this isn’t scaring me away from having the object model that makes most sense.
Re: RefObject and ValObject
It's not bikeshedding, hence the "if you prefer ...", it's trying to explain that it's a typing issue and not a runtime class issue. Rémi - Mail original - > De: "John Rose" > À: "Remi Forax" , "Maurizio Cimadamore" > > Cc: "Brian Goetz" , "valhalla-spec-experts" > > Envoyé: Lundi 15 Avril 2019 20:13:50 > Objet: Re: RefObject and ValObject > Avoid the Bikeshed! Make sure that temporary or provisional > syntaxes look __Different from permanent ones. > > On Apr 15, 2019, at 8:03 AM, Remi Forax wrote: >> >> val, ref and any only applies to Object. >> >> if you prefer in term of notation, let's rename ref Object to Object?, val >> Object to Object! and any Object to Object*, >> >> you can apply ? on any value type, Object included, > > you can apply ! and * only on Object.
Re: RefObject and ValObject
> De: "Brian Goetz" > À: "Remi Forax" > Cc: "Maurizio Cimadamore" , > "valhalla-spec-experts" > Envoyé: Lundi 15 Avril 2019 20:00:52 > Objet: Re: RefObject and ValObject >> It's not a minor change, and all code that uses a type parameter that have >> Object as bound will become ambiguous. > I don’t think so. You can’t say > new T() > when T is bounded at Object (or anything, for that matter.). > What ambiguity are you afraid of here? 1) any codes that has inference var list = List.of(new Foo(), new Bar()); will be inferred as List instead of List, so calling a method that takes a List will not compile anymore. 2) any code that consider Object as special class may stop working, dynamic proxies that have a special case for Object, any code that reflect recursively on the hierarchy to find all the methods, any code that remove getClass() from the getter list, etc. Rémi
Re: RefObject and ValObject
That’s right, as we think that will be the best default. Obviously in some cases users will want to re-bound to . (For existing code; this comes with compatibility concerns, which we can handle in various ways. The standard trick to change a bound without changing erasure is to bound at Object this works when X is an interface but currently would be unhappy if X is a class. But this could be adjusted.) > On Apr 15, 2019, at 4:23 PM, Maurizio Cimadamore > wrote: > > Maybe (Remi correct me if I'm wrong), the problem Remi was referring to is > that we also have existing generic declarations like > which, in the new world, will mean either VALUE or REFERENCE. I think this is > a consequence of the choice (1) I described in my email - e.g. reinterpret > Object in type position as TOP_TYPE. > > Maurizio > > On 15/04/2019 19:00, Brian Goetz wrote: >>> >>> It's not a minor change, and all code that uses a type parameter that have >>> Object as bound will become ambiguous. >> >> I don’t think so. You can’t say >> >> new T() >> >> when T is bounded at Object (or anything, for that matter.). >> >> What ambiguity are you afraid of here? >> >>
Re: RefObject and ValObject
Maybe (Remi correct me if I'm wrong), the problem Remi was referring to is that we also have existing generic declarations like Object> which, in the new world, will mean either VALUE or REFERENCE. I think this is a consequence of the choice (1) I described in my email - e.g. reinterpret Object in type position as TOP_TYPE. Maurizio On 15/04/2019 19:00, Brian Goetz wrote: It's not a minor change, and all code that uses a type parameter that have Object as bound will become ambiguous. I don’t think so. You can’t say new T() when T is bounded at Object (or anything, for that matter.). What ambiguity are you afraid of here?
Re: RefObject and ValObject
I think this hangs together very well. For legacy bytecode (only), the JVM has to be willing to do these one-time fudges: - rewrite new java/lang/Object to new java/lang/RefObject - rewrite invokespecial java/lang/Object.()V to invokespecial java/lang/RefObject.()V The verifier can observe these rewrites, but it may also take this additional step, for legacy code only: - widen the type of new java/lang/RefObject to plain Object (…which would prevent the verifier from passing the new RefObject to a method that actually takes RefObject. Not sure that step is useful; it's a "one hand clapping" type of move, which in practice won't be observable.) — John > On Apr 15, 2019, at 8:23 AM, Brian Goetz wrote: > >> >> Your idea of treating Object as abstract is, I believe, a sound one (which >> doesn't need any extra rule) - but we might have to figure out some story >> for anonymous inner classes of the kind `new Object() { ... }`. > > After thinking about it for all of five minutes, I think this may have broken > the logjam (or, at least the current logjam.). We’ve been asking ourselves > whether RO/VO are classes or interfaces, when we didn’t really consider > abstract classes. Which we didn’t consider because we had assumed that the > concrete-ness of Object was nailed down. Let’s assume it’s not. > > Then we have: > > abstract class Object { } > abstract class RefObject <: Object { } > abstract class ValObject <: Object { } > > Existing classes that extend Object are silently reparented to RefObject, > both at compile time and runtime. This may have some small .getSuperclass() > anomalies but this seems pretty minor. Same with anon classes of Object. > Inline classes implicitly extend ValObject. > > We add a method `Object::newInstance` (name to be bikeshod later.). We start > warning in the compiler on `new Object`, to motivate migration to > `Object::newInstance`. Runtime rewrites these too. > > There are some minor behavioral compatibility issues here, but they seem > pretty minor, and in the end, we end up with a hierarchy that describes the > way we want users to see the type system. > >
Re: RefObject and ValObject
Avoid the Bikeshed! Make sure that temporary or provisional syntaxes look __Different from permanent ones. On Apr 15, 2019, at 8:03 AM, Remi Forax wrote: > > val, ref and any only applies to Object. > > if you prefer in term of notation, let's rename ref Object to Object?, val > Object to Object! and any Object to Object*, > > you can apply ? on any value type, Object included, > you can apply ! and * only on Object.
Re: RefObject and ValObject
> De: "Brian Goetz" > À: "Maurizio Cimadamore" > Cc: "valhalla-spec-experts" > Envoyé: Lundi 15 Avril 2019 17:23:36 > Objet: Re: RefObject and ValObject >> Your idea of treating Object as abstract is, I believe, a sound one (which >> doesn't need any extra rule) - but we might have to figure out some story for >> anonymous inner classes of the kind `new Object() { ... }`. > After thinking about it for all of five minutes, I think this may have broken > the logjam (or, at least the current logjam.). We’ve been asking ourselves > whether RO/VO are classes or interfaces, when we didn’t really consider > abstract classes. Which we didn’t consider because we had assumed that the > concrete-ness of Object was nailed down. Let’s assume it’s not. > Then we have: > abstract class Object { } > abstract class RefObject <: Object { } > abstract class ValObject <: Object { } > Existing classes that extend Object are silently reparented to RefObject, both > at compile time and runtime. This may have some small .getSuperclass() > anomalies but this seems pretty minor. Same with anon classes of Object. > Inline > classes implicitly extend ValObject. > We add a method `Object::newInstance` (name to be bikeshod later.). We start > warning in the compiler on `new Object`, to motivate migration to > `Object::newInstance`. Runtime rewrites these too. > There are some minor behavioral compatibility issues here, but they seem > pretty > minor, and in the end, we end up with a hierarchy that describes the way we > want users to see the type system. It's not a minor change, and all code that uses a type parameter that have Object as bound will become ambiguous. Rémi
Re: RefObject and ValObject
> For (2) the proposal I saw earlier said something like, we'd like for `new > Object` to mean REFERENCE. I think that is a siren song, and this issue has > been addressed by Brian's proposal to deal with `new Object` as a migration > problem. Rather than suggesting to use some static factory instead (which > works for plain instance creation but not for inner class creation) I think > perhaps the user code should be migrated to use `new RefObject` and `new > RefObject() { }` instead (at least if people want the reference semantics). > But these are minor details > What I like about treating this as a migration problem is, that despite the inconvenience, the resulting code is actually _a lot more clear_. The first time I saw “new Object()” (that was a long time ago), I remember thinking “What the heck is the point of that?”, until I realized that Objects had a secret object identity. Whereas “new IdentityObject()” is more clear that you are creating an instance _for the precise purpose of using its identity_. So, while there is some migration pain, the resulting language is actually more clear. I like that.
Re: RefObject and ValObject
Please, no. > On Apr 15, 2019, at 11:03 AM, Remi Forax wrote: > > > > De: "Brian Goetz" > À: "Maurizio Cimadamore" > Cc: "valhalla-spec-experts" > Envoyé: Lundi 15 Avril 2019 16:38:58 > Objet: Re: RefObject and ValObject > Let's model the value vs. reference constraint explicitly - e.g. with 'val' > and 'ref' type kinds (and let's not open the can worm as to what the syntax > should be, whether it should be a type anno, etc.) > > So: > > val Object ---> accepts all values > ref Object ---> accepts all references > any Object ---> accepts all references > > > We explored this sort of thing in Q world. One place where this really was > painful was what it did to generic tvar constraints; we had an entire new > algebra of constraints: > > void m(T t) { … } > > Brian, > val, ref and any only applies to Object. > > if you prefer in term of notation, let's rename ref Object to Object?, val > Object to Object! and any Object to Object*, > > you can apply ? on any value type, Object included, > you can apply ! and * only on Object. > > Object? is erased to Ljava/lang/Object; by the generic signature is > Ljava/lang/Object/* (so it's Object for the VM and Object! for the compiler), > Object! is erased to Qjav/lang/Object; > Object* is erased to Ljava/lang/Object; > > As a bound of a type variable, Object is equivalent to Object? by backward > compatibility. > and >void m(T t) { … } > means that T is not nullable, so it's a value type. > > And at runtime, instead of >(o instanceof Object!) > one will write > o.getClass().isValue() > > Rémi > > > which was ad hoc and composed terribly. This hell is _exactly_ the thing > that pushed us to Ref/ValObject as _types_ in the first place. (More of the > same: what is “ref Object”.class, and how does it differ from “any > Object”.class?). > > 2) reinterpret `Object` as `any Object` > > That is, the semantics of `Object` is being redefined here - code which > assumed to work with references might need to opt-in to additional > constraints (e.g. add `ref`) in order to make sure it still work as intended. > > Right. Q-world tried it the other way, and we were in utter migration hell. > There are migration cases in this direction too, but we are convinced they > are orders of magnitude fewer. > I don't see another way out of this conundrum - other than adding a special > rule (z2) which says that `new Object()` is treated specially and always has > kind `ref`. But doing so will run afoul in almost every possible way - as > soon as you manipulate the result of the `new` in any way (cast, assignment > to variable of type `Object`, ...) you go back to `any` and you are back to a > place that is incompatible with `ref Object`. > > Yes, this is the cost. I have to think that given a choice between some > weirdness around ’new Object’, and dramatic, awful new kinds of types that > complicate type uses, type descriptors, reflection, etc, etc, etc, that > making the former work is going to be less painful, both for us and for users. > > Your idea of treating Object as abstract is, I believe, a sound one (which > doesn't need any extra rule) - but we might have to figure out some story for > anonymous inner classes of the kind `new Object() { ... }`. > > > Right. And, again, this can be treated as a migration issue, and we can > start warning users to migrate their source now.
Re: RefObject and ValObject
> > Your idea of treating Object as abstract is, I believe, a sound one (which > doesn't need any extra rule) - but we might have to figure out some story for > anonymous inner classes of the kind `new Object() { ... }`. After thinking about it for all of five minutes, I think this may have broken the logjam (or, at least the current logjam.). We’ve been asking ourselves whether RO/VO are classes or interfaces, when we didn’t really consider abstract classes. Which we didn’t consider because we had assumed that the concrete-ness of Object was nailed down. Let’s assume it’s not. Then we have: abstract class Object { } abstract class RefObject <: Object { } abstract class ValObject <: Object { } Existing classes that extend Object are silently reparented to RefObject, both at compile time and runtime. This may have some small .getSuperclass() anomalies but this seems pretty minor. Same with anon classes of Object. Inline classes implicitly extend ValObject. We add a method `Object::newInstance` (name to be bikeshod later.). We start warning in the compiler on `new Object`, to motivate migration to `Object::newInstance`. Runtime rewrites these too. There are some minor behavioral compatibility issues here, but they seem pretty minor, and in the end, we end up with a hierarchy that describes the way we want users to see the type system.
Re: RefObject and ValObject
> De: "Brian Goetz" > À: "Maurizio Cimadamore" > Cc: "valhalla-spec-experts" > Envoyé: Lundi 15 Avril 2019 16:38:58 > Objet: Re: RefObject and ValObject >> Let's model the value vs. reference constraint explicitly - e.g. with 'val' >> and >> 'ref' type kinds (and let's not open the can worm as to what the syntax >> should >> be, whether it should be a type anno, etc.) >> So: >> val Object ---> accepts all values >> ref Object ---> accepts all references >> any Object ---> accepts all references > We explored this sort of thing in Q world. One place where this really was > painful was what it did to generic tvar constraints; we had an entire new > algebra of constraints: > void m(T t) { … } Brian, val, ref and any only applies to Object. if you prefer in term of notation, let's rename ref Object to Object?, val Object to Object! and any Object to Object*, you can apply ? on any value type, Object included, you can apply ! and * only on Object. Object? is erased to Ljava/lang/Object; by the generic signature is Ljava/lang/Object/* (so it's Object for the VM and Object! for the compiler), Object! is erased to Qjav/lang/Object; Object* is erased to Ljava/lang/Object; As a bound of a type variable, Object is equivalent to Object? by backward compatibility. and void m(T t) { … } means that T is not nullable, so it's a value type. And at runtime, instead of (o instanceof Object!) one will write o.getClass().isValue() Rémi > which was ad hoc and composed terribly. This hell is _exactly_ the thing that > pushed us to Ref/ValObject as _types_ in the first place. (More of the same: > what is “ref Object”.class, and how does it differ from “any Object”.class?). >> 2) reinterpret `Object` as `any Object` >> That is, the semantics of `Object` is being redefined here - code which >> assumed >> to work with references might need to opt-in to additional constraints (e.g. >> add `ref`) in order to make sure it still work as intended. > Right. Q-world tried it the other way, and we were in utter migration hell. > There are migration cases in this direction too, but we are convinced they are > orders of magnitude fewer. >> I don't see another way out of this conundrum - other than adding a special >> rule >> (z2) which says that `new Object()` is treated specially and always has kind >> `ref`. But doing so will run afoul in almost every possible way - as soon as >> you manipulate the result of the `new` in any way (cast, assignment to >> variable >> of type `Object`, ...) you go back to `any` and you are back to a place that >> is >> incompatible with `ref Object`. > Yes, this is the cost. I have to think that given a choice between some > weirdness around ’new Object’, and dramatic, awful new kinds of types that > complicate type uses, type descriptors, reflection, etc, etc, etc, that making > the former work is going to be less painful, both for us and for users. >> Your idea of treating Object as abstract is, I believe, a sound one (which >> doesn't need any extra rule) - but we might have to figure out some story for >> anonymous inner classes of the kind `new Object() { ... }`. > Right. And, again, this can be treated as a migration issue, and we can start > warning users to migrate their source now.
Re: RefObject and ValObject
> > Well, not quite. The choice is between treating 'new Object' specially or > not. If it's not treated specially there is no new scary type. It's just > that`new Object` might not have the properties one might hope for (but again, > those same properties would be lost as soon as you touch the result of the > expression). OK, but let’s not lose sight of the fact that “new Object()” is not so much a language _feature_, as much as an accidental convention that people have settled on for accomplishing a very specific thing. In a way, it’s like the so-called-but-not-really-a-feature double-brace idiom; it’s an accidental consequence of how the language works, that people have come to use because it’s convenient. I would much rather spend our complexity budget migrating uses of this idiom away and get a simpler type system, than the other way around.
Re: RefObject and ValObject
On 15/04/2019 15:38, Brian Goetz wrote: Let's model the value vs. reference constraint explicitly - e.g. with 'val' and 'ref' type kinds (and let's not open the can worm as to what the syntax should be, whether it should be a type anno, etc.) So: val Object ---> accepts all values ref Object ---> accepts all references any Object ---> accepts all references We explored this sort of thing in Q world. One place where this really was painful was what it did to generic tvar constraints; we had an entire new algebra of constraints: void m(T t) { … } which was ad hoc and composed terribly. This hell is _exactly_ the thing that pushed us to Ref/ValObject as _types_ in the first place. (More of the same: what is “ref Object”.class, and how does it differ from “any Object”.class?). This is not a language proposal (as stated in the email). Just a way to be clearer about semantics, w/o appealing to subclasses and type hierarchies (which are, at this point, IMHO confusing). 2) reinterpret `Object` as `any Object` That is, the semantics of `Object` is being redefined here - code which assumed to work with references might need to opt-in to additional constraints (e.g. add `ref`) in order to make sure it still work as intended. Right. Q-world tried it the other way, and we were in utter migration hell. There are migration cases in this direction too, but we are convinced they are orders of magnitude fewer. I don't see another way out of this conundrum - other than adding a special rule (z2) which says that `new Object()` is treated specially and always has kind `ref`. But doing so will run afoul in almost every possible way - as soon as you manipulate the result of the `new` in any way (cast, assignment to variable of type `Object`, ...) you go back to `any` and you are back to a place that is incompatible with `ref Object`. Yes, this is the cost. I have to think that given a choice between some weirdness around ’new Object’, and dramatic, awful new kinds of types that complicate type uses, type descriptors, reflection, etc, etc, etc, that making the former work is going to be less painful, both for us and for users. Well, not quite. The choice is between treating 'new Object' specially or not. If it's not treated specially there is no new scary type. It's just that`new Object` might not have the properties one might hope for (but again, those same properties would be lost as soon as you touch the result of the expression). I think you took my type modifiers too literally (as if, I'm proposing to add them, which I'm not) :-) Maurizio Your idea of treating Object as abstract is, I believe, a sound one (which doesn't need any extra rule) - but we might have to figure out some story for anonymous inner classes of the kind `new Object() { ... }`. Right. And, again, this can be treated as a migration issue, and we can start warning users to migrate their source now.
Re: RefObject and ValObject
> Let's model the value vs. reference constraint explicitly - e.g. with 'val' > and 'ref' type kinds (and let's not open the can worm as to what the syntax > should be, whether it should be a type anno, etc.) > > So: > > val Object ---> accepts all values > ref Object ---> accepts all references > any Object ---> accepts all references > We explored this sort of thing in Q world. One place where this really was painful was what it did to generic tvar constraints; we had an entire new algebra of constraints: void m(T t) { … } which was ad hoc and composed terribly. This hell is _exactly_ the thing that pushed us to Ref/ValObject as _types_ in the first place. (More of the same: what is “ref Object”.class, and how does it differ from “any Object”.class?). > 2) reinterpret `Object` as `any Object` > > That is, the semantics of `Object` is being redefined here - code which > assumed to work with references might need to opt-in to additional > constraints (e.g. add `ref`) in order to make sure it still work as intended. > Right. Q-world tried it the other way, and we were in utter migration hell. There are migration cases in this direction too, but we are convinced they are orders of magnitude fewer. > I don't see another way out of this conundrum - other than adding a special > rule (z2) which says that `new Object()` is treated specially and always has > kind `ref`. But doing so will run afoul in almost every possible way - as > soon as you manipulate the result of the `new` in any way (cast, assignment > to variable of type `Object`, ...) you go back to `any` and you are back to a > place that is incompatible with `ref Object`. > Yes, this is the cost. I have to think that given a choice between some weirdness around ’new Object’, and dramatic, awful new kinds of types that complicate type uses, type descriptors, reflection, etc, etc, etc, that making the former work is going to be less painful, both for us and for users. > Your idea of treating Object as abstract is, I believe, a sound one (which > doesn't need any extra rule) - but we might have to figure out some story for > anonymous inner classes of the kind `new Object() { ... }`. > Right. And, again, this can be treated as a migration issue, and we can start warning users to migrate their source now.
Re: RefObject and ValObject
Mu suggestion here is to leave subtyping on the side, at least for now and use some other way to describe what's going on. Let's model the value vs. reference constraint explicitly - e.g. with 'val' and 'ref' type kinds (and let's not open the can worm as to what the syntax should be, whether it should be a type anno, etc.) So: val Object ---> accepts all values ref Object ---> accepts all references any Object ---> accepts all references Now that everything is explicit, for types we have two possible moves: 1) reinterpret `Object` as `ref Object` This will keep semantics as is - that is, upon recompilation, if source code doesn't change, a program that expected references cannot start receiving value parameters. If code wants to work on both references and values, it will have to opt in (by using `any`). 2) reinterpret `Object` as `any Object` That is, the semantics of `Object` is being redefined here - code which assumed to work with references might need to opt-in to additional constraints (e.g. add `ref`) in order to make sure it still work as intended. I think we are leaning towards (2) - that is, we want meaning of Object to be upgraded, and we want users that are not happy with that to opt out in some form. Ok, now let's think about expressions; given an expression E, I have to figure out (i) its type and its (ii) kind, since the type system I'm describing here takes both factors into account. Here I'm expecting rules of the kind: a) if E is an identifier pointing to a variable decl, then type and kind are derived from the declaration b) if E is a method call, where declared method is M, type and kind are derived from M's return type declaration ... z) if E is a new expression, of the kind `new T()`, the type is T and the kind can be either `ref` or `val` depending on whether T is a reference class or not. If T can be both, then kind `any` is inferred. So, we can use (z) e.g. to say that `new String()` has kind `ref`. But, if we want Object to be the top type for both values and references, I believe one consequence is that `new Object` is interpreted as `any` which means you cannot pass it to `ref Object`. I don't see another way out of this conundrum - other than adding a special rule (z2) which says that `new Object()` is treated specially and always has kind `ref`. But doing so will run afoul in almost every possible way - as soon as you manipulate the result of the `new` in any way (cast, assignment to variable of type `Object`, ...) you go back to `any` and you are back to a place that is incompatible with `ref Object`. Your idea of treating Object as abstract is, I believe, a sound one (which doesn't need any extra rule) - but we might have to figure out some story for anonymous inner classes of the kind `new Object() { ... }`. Maurizio On 15/04/2019 14:26, Brian Goetz wrote: But the other concerns remain, e.g. as to the fact that the boundary between reinterpreted types (Object as RefObject) and non-reinterpreted types (Object as top type) seems very fuzzy. Right, which is why we’re still searching for an answer :) We really, really want to be able to represent ref/val-ness in the type system. Why? Ignoring pedagogical concerns (which are significant): - If certain operations (e.g., locking) are partial, we want to be able to provide a way to ask if the operation could succeed. Such as: if (x instanceof RefObejct) { … lock on x … } Saying “lock, and hope it doesn’t throw” is not a very good answer. We already have a tool for querying the dynamic type of an object — instanceof. - Saying that a method should only accept reference objects should be something expressible in the method signature, as in m(RefObject o) { … } Types are how we do that. - Similarly, we might want to express the above constraint generically; again, types are the way we do that: class Foo { } And, Q-world already taught us that we wanted to retain Object as the top type. This mean, necessarily, that Object gets a little weirder; it takes on some partly-class, partly-interface behavior. Here’s an idea: What if we migrated `Object` to be an abstract class? The casualty would be the code that says `new Object()`. While there’s certainly a lot of it out there, perhaps this is something amenable to migration: - At the source level, for N versions, `new Object()` gets a warning that says “I’ll pretend you said `Object.newLockInstance()` or something. - At the bytemode level, for M versions, we do something similar, likely for M > N. We can start this now, before Valhalla even previews.
Re: RefObject and ValObject
> But the other concerns remain, e.g. as to the fact that the boundary between > reinterpreted types (Object as RefObject) and non-reinterpreted types (Object > as top type) seems very fuzzy. Right, which is why we’re still searching for an answer :) We really, really want to be able to represent ref/val-ness in the type system. Why? Ignoring pedagogical concerns (which are significant): - If certain operations (e.g., locking) are partial, we want to be able to provide a way to ask if the operation could succeed. Such as: if (x instanceof RefObejct) { … lock on x … } Saying “lock, and hope it doesn’t throw” is not a very good answer. We already have a tool for querying the dynamic type of an object — instanceof. - Saying that a method should only accept reference objects should be something expressible in the method signature, as in m(RefObject o) { … } Types are how we do that. - Similarly, we might want to express the above constraint generically; again, types are the way we do that: class Foo { } And, Q-world already taught us that we wanted to retain Object as the top type. This mean, necessarily, that Object gets a little weirder; it takes on some partly-class, partly-interface behavior. Here’s an idea: What if we migrated `Object` to be an abstract class? The casualty would be the code that says `new Object()`. While there’s certainly a lot of it out there, perhaps this is something amenable to migration: - At the source level, for N versions, `new Object()` gets a warning that says “I’ll pretend you said `Object.newLockInstance()` or something. - At the bytemode level, for M versions, we do something similar, likely for M > N. We can start this now, before Valhalla even previews.
Re: RefObject and ValObject
- Mail original - > De: "Maurizio Cimadamore" > À: "Brian Goetz" , "valhalla-spec-experts" > > Envoyé: Lundi 15 Avril 2019 14:20:35 > Objet: Re: RefObject and ValObject > On 15/04/2019 13:06, Brian Goetz wrote: >> >> >>> But it seems like we have already ruled this out - since, if >>> typeof(new Object()) is 'RefObject', you don't want RefObject <: Object. >> >> I think you misunderstood the "want" here (though it still may not be >> possible.) >> >> The desired model is: >> - Object is the top type. Everything is an Object. >> - Some objects have identity (RefObject), others do not (ValObject). >> But they are all Object. >> >> This means we want {Ref,Val}Object <: Object. (Whether they are >> interface or class or something else.) > > Seems like I've read this requirement: > > "but we don’t want Object <: RefObject for obvious reasons" > > backwards. So, what this means is that it would be type-sound regardless > of interface vs. class choice. But the other concerns remain, e.g. as to > the fact that the boundary between reinterpreted types (Object as > RefObject) and non-reinterpreted types (Object as top type) seems very > fuzzy. yes, divorcing the runtime class from the type is something we can do, but it's not because we can do that we should, as a teacher you usually don't want to talk about the difference between a class and a type until you have reached the subtyping chapter. The initial goal is to make the concept of ref type and value type easier to grasp by providing a simple hierarchy, but new Object() can not be retconed to a RefObject easily, so it's not that simple. And Ruby has tried to do something similar by introducing BasicObject in 1.9 (for another reason, because scopes are liked to classes in Ruby), at the end few people cares, so we have to be careful to not introduce something that will be a hurdle in compatibility for in the end no benefit. So we need to be able to have types representing any Object, ref Object and value Object, but i think that RefObject and ValObject are not the only solution for that. > > Maurizio Rémi > >> >> One of the main reasons for wanting this setup is that it reflects the >> desired reality: everything is an object, but some are special objects >> (those with identity.) The addition of value types is a big >> perturbation to the type system; reflecting it this way makes the >> object hierarchy reflect the reality and the desired intuition, and >> makes the distinction between ref/val slightly less magic. >> >> (There are other reasons too; for example, wouldn't it be nice if >> ValObject.{wait,notify,notifyAll} were _ordinary final methods_ that >> threw in ValObject? Again, slightly less magic.) >>
Re: RefObject and ValObject
On 15/04/2019 13:06, Brian Goetz wrote: But it seems like we have already ruled this out - since, if typeof(new Object()) is 'RefObject', you don't want RefObject <: Object. I think you misunderstood the "want" here (though it still may not be possible.) The desired model is: - Object is the top type. Everything is an Object. - Some objects have identity (RefObject), others do not (ValObject). But they are all Object. This means we want {Ref,Val}Object <: Object. (Whether they are interface or class or something else.) Seems like I've read this requirement: "but we don’t want Object <: RefObject for obvious reasons" backwards. So, what this means is that it would be type-sound regardless of interface vs. class choice. But the other concerns remain, e.g. as to the fact that the boundary between reinterpreted types (Object as RefObject) and non-reinterpreted types (Object as top type) seems very fuzzy. Maurizio One of the main reasons for wanting this setup is that it reflects the desired reality: everything is an object, but some are special objects (those with identity.) The addition of value types is a big perturbation to the type system; reflecting it this way makes the object hierarchy reflect the reality and the desired intuition, and makes the distinction between ref/val slightly less magic. (There are other reasons too; for example, wouldn't it be nice if ValObject.{wait,notify,notifyAll} were _ordinary final methods_ that threw in ValObject? Again, slightly less magic.)
Re: RefObject and ValObject
But it seems like we have already ruled this out - since, if typeof(new Object()) is 'RefObject', you don't want RefObject <: Object. I think you misunderstood the "want" here (though it still may not be possible.) The desired model is: - Object is the top type. Everything is an Object. - Some objects have identity (RefObject), others do not (ValObject). But they are all Object. This means we want {Ref,Val}Object <: Object. (Whether they are interface or class or something else.) One of the main reasons for wanting this setup is that it reflects the desired reality: everything is an object, but some are special objects (those with identity.) The addition of value types is a big perturbation to the type system; reflecting it this way makes the object hierarchy reflect the reality and the desired intuition, and makes the distinction between ref/val slightly less magic. (There are other reasons too; for example, wouldn't it be nice if ValObject.{wait,notify,notifyAll} were _ordinary final methods_ that threw in ValObject? Again, slightly less magic.)
Re: RefObject and ValObject
My original thought had been that this would benefit new code that knew it had to do something identity-full (lock, make weak references, etc) and wanted to enforce it would never see a value. The migration path adds a cherry on top. --Dan - Original message -From: Brian Goetz To: Daniel Heidinga Cc: valhalla-spec-experts Subject: Re: RefObject and ValObjectDate: Fri, Apr 12, 2019 11:51 AM High-order tradeoffs: - Having R/VObject be classes helps from the pedagogical perspective(it paints an accurate map of the object model.) - There were some anomalies raised that were the result of rewritingan Object supertype to RefObject, and some concerns about "all ourtables got one level deeper." I don't really have a strong opinion onthese. - Using interfaces is less intrusive, but less powerful. - None of the approaches give an obvious solution for the "make me alock Object" problem.I think the useful new observation in this line of discussion is this: - The premise of L-World is that legacy Object-consuming code can keepworking with values. - We think that's a good thing. - But we also think there will be some cases where that's not agood thing, and that code will wish it had said `m(RefObject)` insteadof `m(Object)`. [ this is the new thing ]Combining this with the migration stuff going on in a separate thread, Ithink what you're saying is you want to be able to take a method: m(Object o) { }and _migrate_ it to be m(RefObject o) { }with a forwarder @ForwardTo( m(RefObject) ) m(Object o);So that code could, eventually, be migrated to RefObject-consuming code,and all is good again. And the JIT can see that o is a RefObject andcredibly fall back to a legacy interpretation of ACMP and locking.On 4/12/2019 11:16 AM, Daniel Heidinga wrote:> During the last EG call, I suggested there are benefits to having both RefObject and ValObject be classes rather than interfaces.>> Old code should be able work with both values and references (that's the promise of L-World after all!). New code should be able to opt into whether it wants to handle only references or values as there are APIs that may only make sense for one or the other. A good example of this is java.lang.Reference-subtypes which can't reasonably deal with values. Having RefObject in their method signatures would ensure that they're never passed a ValObject. (ie: the ctor becomes WeakReference(RefObject o) {...})>> For good or ill, interfaces are not checked by the verifier. They're passed as though they are object and the interface check is delayed until invokeinterface, etc. Using interfaces for Ref/Val Object doesn't provide verifier guarantees that the methods will never be passed the wrong type. Javac may not generate the code but the VM can't count on that being the case due to bytecode instrumentation, other compilers, etc.>> Using classes does provide a strong guarantee to the VM which will help to alleviate any costs (acmp, array access) for methods that are declared in terms of RefObject and ensures that the user is getting exactly what they asked for when they declared their method to take RefObject.>> It does leave some oddities as you mention:> * new Object() -> returns a new RefObject> * getSuperclass() for old code may return a new superclass (though this may be the case already when using instrumentation in the classfile load hook)> * others?>> though adding interfaces does as well:> * getInterfaces() would return an interface not declared in the source> * Object would need to implement RefObject for the 'new Object()` case which would mean all values implemented RefObject (yuck!)>> Letting users say what they mean and have it strongly enforced by the verifier is preferable in my view, especially as getSuperclass() issue will only apply to old code as newly compiled code will have the correct superclass in its classfile.>> --Dan>>> -"valhalla-spec-experts" wrote: ->>> To: valhalla-spec-experts >> From: Brian Goetz>> Sent by: "valhalla-spec-experts">> Date: 04/08/2019 04:00PM>> Subject: RefObject and ValObject We never reached consensus on how to surface Ref/ValObject. Here are some places we might want to use these type names: - Parameter types / variables: we might want to restrict the domain>> of a parameter or variable to only hold a reference, or a value: void m(RefObject ro) { … } - Type bounds: we might want to restrict the instantiation of a>> generic class to only hold a reference (say, because we’re going to>> lock on it): class Foo { … } - Dynamic tests: if locking on a value is to throw, there must be a>> reasonable idiom that users can use to detect lockability without>> just trying to lock: if (x instanceof RefObject) {>> synchronized(x) { … }>> } - Ref- or Val-specific methods. This one is more vague, but its>> conceivable we may want methods on ValObject that are members of all>> values.>> There’s been three ways proposed (so far) that we
Re: RefObject and ValObject
High-order tradeoffs: - Having R/VObject be classes helps from the pedagogical perspective (it paints an accurate map of the object model.) - There were some anomalies raised that were the result of rewriting an Object supertype to RefObject, and some concerns about "all our tables got one level deeper." I don't really have a strong opinion on these. - Using interfaces is less intrusive, but less powerful. - None of the approaches give an obvious solution for the "make me a lock Object" problem. I think the useful new observation in this line of discussion is this: - The premise of L-World is that legacy Object-consuming code can keep working with values. - We think that's a good thing. - But we also think there will be some cases where that's not a good thing, and that code will wish it had said `m(RefObject)` instead of `m(Object)`. [ this is the new thing ] Combining this with the migration stuff going on in a separate thread, I think what you're saying is you want to be able to take a method: m(Object o) { } and _migrate_ it to be m(RefObject o) { } with a forwarder @ForwardTo( m(RefObject) ) m(Object o); So that code could, eventually, be migrated to RefObject-consuming code, and all is good again. And the JIT can see that o is a RefObject and credibly fall back to a legacy interpretation of ACMP and locking. On 4/12/2019 11:16 AM, Daniel Heidinga wrote: During the last EG call, I suggested there are benefits to having both RefObject and ValObject be classes rather than interfaces. Old code should be able work with both values and references (that's the promise of L-World after all!). New code should be able to opt into whether it wants to handle only references or values as there are APIs that may only make sense for one or the other. A good example of this is java.lang.Reference-subtypes which can't reasonably deal with values. Having RefObject in their method signatures would ensure that they're never passed a ValObject. (ie: the ctor becomes WeakReference(RefObject o) {...}) For good or ill, interfaces are not checked by the verifier. They're passed as though they are object and the interface check is delayed until invokeinterface, etc. Using interfaces for Ref/Val Object doesn't provide verifier guarantees that the methods will never be passed the wrong type. Javac may not generate the code but the VM can't count on that being the case due to bytecode instrumentation, other compilers, etc. Using classes does provide a strong guarantee to the VM which will help to alleviate any costs (acmp, array access) for methods that are declared in terms of RefObject and ensures that the user is getting exactly what they asked for when they declared their method to take RefObject. It does leave some oddities as you mention: * new Object() -> returns a new RefObject * getSuperclass() for old code may return a new superclass (though this may be the case already when using instrumentation in the classfile load hook) * others? though adding interfaces does as well: * getInterfaces() would return an interface not declared in the source * Object would need to implement RefObject for the 'new Object()` case which would mean all values implemented RefObject (yuck!) Letting users say what they mean and have it strongly enforced by the verifier is preferable in my view, especially as getSuperclass() issue will only apply to old code as newly compiled code will have the correct superclass in its classfile. --Dan -"valhalla-spec-experts" wrote: - To: valhalla-spec-experts From: Brian Goetz Sent by: "valhalla-spec-experts" Date: 04/08/2019 04:00PM Subject: RefObject and ValObject We never reached consensus on how to surface Ref/ValObject. Here are some places we might want to use these type names: - Parameter types / variables: we might want to restrict the domain of a parameter or variable to only hold a reference, or a value: void m(RefObject ro) { … } - Type bounds: we might want to restrict the instantiation of a generic class to only hold a reference (say, because we’re going to lock on it): class Foo { … } - Dynamic tests: if locking on a value is to throw, there must be a reasonable idiom that users can use to detect lockability without just trying to lock: if (x instanceof RefObject) { synchronized(x) { … } } - Ref- or Val-specific methods. This one is more vague, but its conceivable we may want methods on ValObject that are members of all values. There’s been three ways proposed (so far) that we might reflect these as top types: - RefObject and ValObject are (somewhat special) classes. We spell (at least in the class file) “value class” as “class X extends ValObject”. We implicitly rewrite reference classes at runtime that extend Object to extend RefObject instead. This has obvious pedagogical value, but there are some (small) risks of anomalies. - RefObject and ValObject are interfaces.
Re: RefObject and ValObject
Yes, that's a nice one. On Apr 8, 2019, at 1:24 PM, Brian Goetz wrote: > > Sergey pointed out this additional benefit of RefObject: code that really > doesn’t want to deal with values, or pay any of the taxes that arise from the > fact that values are Object, such as acmp overhead.