Right. And Peter’s question is: (a) did we think of this (yes) and (b) are we OK with this. Which I think is also yes?
> On May 23, 2016, at 7:18 AM, Maurizio Cimadamore > <[email protected]> wrote: > > Sorry - I now realize that the point I made in my earlier email was unclear. > > What I'm suggesting is to have a single rule for generating unchecked > warnings that goes like this: > > "If the qualifier of a species static access is not reifiable, an unchecked > warning should occur". > > In the example Peter sent, the only thing worth mentioning is that the > qualifier is 'implicit' (i.e. can be omitted and be assumed to be the current > class Foo<T>); now since Foo<T> is not reifiable, every unqualified access to > 'st' from Foo<T> will get a warning - excluding, of course, accesses > occurring in a context where T is restricted (i.e. __WhereVal(T)). > > Maurizio > > On 23/05/16 14:56, Brian Goetz wrote: >> Note that we have this same problem with unchecked warnings today in many of >> the use cases. For example, in the “cached empty list” case, we always have >> to use an unchecked cast to cast the cached list to the desired type. When >> we use species-static to do the same, and it is possible that the species >> could correspond to more than one T, we still have to do the same unchecked >> warning (and as you mention, the singleton form has the same problem.) I >> think its an unescapable consequence of erasure, but one we’re already sort >> of comfortable with. >> >> If you use a more constrained type selector (e.g., List<int>), you won’t get >> a warning, as the compiler will know that st is exactly int. >> >>> On May 23, 2016, at 3:05 AM, Maurizio Cimadamore >>> <[email protected] <mailto:[email protected]>> >>> wrote: >>> >>> Hi Peter, >>> are you sure we need special treatment for 'it = st' ? After all, the >>> compiler will issue unchecked warnings every time you'll try to access a >>> species static from a non-reifiable type i.e. >>> >>> Foo<String>.st = ""; //warn >>> Foo<int>.st = 42; //no warn >>> >>> In other words, can we put the burden of heap pollution-ness on the client >>> and be happy? >>> >>> Maurizio >>> >>> On 22/05/16 23:58, Peter Levart wrote: >>>> Hi Brian, >>>> >>>> I agree that "species" placement is a better, less verbose option. But how >>>> to solve the language problem of having "species" and "instance" members >>>> of the same "type-variable" type be assignable to one-another? For example: >>>> >>>> class Foo<any T> { >>>> species T st; >>>> T it; >>>> >>>> void m() { >>>> it = st; // this can not be allowed >>>> st = it; // this can be allowed >>>> >>>> // maybe this could be allowed? >>>> @SuppressWarnings("unchecked") >>>> it = (T) st; >>>> } >>>> >>>> >>>> Singleton abstraction has the same problem. >>>> >>>> So while technically possible, it would be weird to have 'T' sometimes not >>>> be assignable to 'T'. Can we live with that? >>>> >>>> Regards, Peter >>>> >>>> On 05/19/2016 04:36 PM, Brian Goetz wrote: >>>>> We discussed two primary means to surface species-specific members in the >>>>> language: a "species" placement (name TBD) as distinct from static and >>>>> instance, or a "singleton" abstraction (a la Scala's "object" >>>>> abstraction, as Peter L suggested). We've done some experiments >>>>> comparing the two approaches. >>>>> >>>>> Separately, we discussed two strategies for handling this at the VM >>>>> level: having three separate placements (ACC_STATIC, ACC_SPECIES, and >>>>> instance) or retconning ACC_STATIC to mean "species" and using compiler >>>>> trickery to simulate traditional statics. In recent discussions with >>>>> Oracle and IBM VM folks, they seemed happy enough with having a new >>>>> placement (and possibly new bytecodes, {get,put,invoke}species, or >>>>> overloading these onto *static with ParamTypes in the owner field of the >>>>> various XxxRef constants.) >>>>> >>>>> >>>>> There are several places where the language itself can take advantage of >>>>> species members: >>>>> >>>>> 1. Reifying type variables. For an any-generic class Foo<T,U>, the >>>>> compiler can generate public static final reflection-thingie-valued >>>>> fields called "T" and "U", which means that "aFoo.T" (as an ordinary >>>>> field ref!) would evaluate to the reflective mirror for the reified T -- >>>>> if present, otherwise it would evaluate to the reflective mirror for >>>>> 'erased'. >>>>> >>>>> 2. Representation of generic methods. The current translation strategy >>>>> has us translating any-generic methods to >>>>> classes; a static method >>>>> >>>>> static<any T> void foo(T t) { } >>>>> >>>>> translates to a class (plus an erased bridge): >>>>> >>>>> bridge static foo(Object o) { ... invoke erased specialization ... } >>>>> >>>>> static class Xxx$foo<any T> { >>>>> void foo(T t) { ... } >>>>> } >>>>> >>>>> This means that an instance of Xxx$foo is needed to invoke the method -- >>>>> but serves solely to carry the type variables -- which is unfortunate. >>>>> If instead we translate as: >>>>> >>>>> static class Xxx$foo<any T> { >>>>> species-static void foo(T t) { ... } >>>>> } >>>>> >>>>> then we can invoke this method via invokespecies: >>>>> >>>>> invokespecies ParamType[Xxx$foo, T_inf].foo(T_inf) >>>>> >>>>> where T_inf is the erasure-normalized type inferred for T (reified if >>>>> value, `erased` reference.) No fake receiver required. >>>>> >>>>> The translation for generic instance methods is still somewhat messier >>>>> (will post separately), but still less messy than if we also had to >>>>> manage / cache a receiver. >>>>> >>>>> >>>>> We also drafted some examples of how such a facility would be used, >>>>> writing them both with species-static and with singleton. Examples and >>>>> notes below; the summary is that in all cases, the species-static version >>>>> is either better or about as good. >>>>> >>>>> >>>>> >>>>> 1. The old favorite, caching an instantiated instance. >>>>> >>>>> Species >>>>> Singleton >>>>> class Collections { >>>>> private static class Holder<any T> { >>>>> private species List<T> empty = new EmptyList<T>(); >>>>> } >>>>> >>>>> static<any T> List<T> emptyList() { return Holder<T>.empty; } >>>>> } >>>>> class Collections { >>>>> private singleton Holder<any T> { >>>>> private empty = new EmptyList<T>(); >>>>> } >>>>> >>>>> static<any T> List<T> emptyList() { return Holder<T>.empty; } >>>>> } >>>>> Note that in this case, species by itself isn't enough -- we still need a >>>>> holder class, and its a bit ugly. Arguably we could merge Holder into >>>>> EmptyList (if that's under our control) but because Collections is an >>>>> old-style "static bag" class (aka "sin bin"), we would still need a >>>>> holder class for state. (Collections could share a single holder for >>>>> multiple things; empty list, empty set, etc.) >>>>> >>>>> Neither the left nor the right seems particularly better than the other >>>>> here. (If we were putting this method on Collection, where it would >>>>> likely go in new code since now interfaces can have statics, the species >>>>> approach would win, since we'd not need the holder class any more.) >>>>> >>>>> >>>>> 2. Instantiation tracking. >>>>> >>>>> Species >>>>> Singleton >>>>> class Foo<any T> { >>>>> private species int count; >>>>> private species List<Foo<T>> foos; >>>>> >>>>> public Foo() { >>>>> ++count; >>>>> foos.add(this); >>>>> } >>>>> } >>>>> class Foo<any T> { >>>>> private singleton FooStuff<T> { >>>>> private int count; >>>>> private List<Foo<T>> foos; >>>>> } >>>>> >>>>> public Foo() { >>>>> ++Foo<T>.count; >>>>> Foo<T>.foos.add(this); >>>>> } >>>>> } >>>>> Because the state is directly tied to the instantiation, the left seems >>>>> more attractive -- doesn't require an extra artifact, and the constructor >>>>> body seems more straightforward. >>>>> >>>>> >>>>> 3. Implicit-like associations. Here, we're caching type associations. >>>>> For example, suppose we have a Box<T>, and we want to cache the >>>>> associated class for List<T>. >>>>> >>>>> >>>>> Species >>>>> Singleton >>>>> class Box<any T> { >>>>> private species Class<List<T>> listClass >>>>> = Class.forSpecialization(List, T.crass); >>>>> } >>>>> class Box<any T> { >>>>> private singleton ListBuddy<any T> { >>>>> Class<List<T>> clazz >>>>> = Class.forSpecialization(List, T.crass); >>>>> } >>>>> } >>>>> The extra singleton declaration feels like "noise" here, because again >>>>> the association is with the full set of type args for the class. >>>>> >>>>> >>>>> 4. Static factories. Arguably, it makes sense to move factories to the >>>>> types they describe. >>>>> >>>>> Species >>>>> Singleton >>>>> interface List<any T> { >>>>> private species List<T> empty = new EmptyList<>(); >>>>> species List<T> emptyList() { return empty; } >>>>> } >>>>> interface List<any T> { >>>>> private singleton Stuff<any T> { >>>>> List<T> empty = new EmptyList<>(); >>>>> } >>>>> species List<T> emptyList() { return Stuff<T>.empty; } >>>>> } >>>>> In this model, you'd get an empty list with >>>>> >>>>> List<T> aList = List<T>.empty() >>>>> rather than >>>>> List<T> aList = Collections.<T>empty(); >>>>> >>>>> In the latter, the type witnesses can be omitted; in the former they >>>>> probably can be as well but that's something new. >>>>> >>>>> >>>>> 5. Typevar shredding. Here, we have separate state for different >>>>> subsets of variables. This should be the place where the singleton >>>>> approach shines. >>>>> >>>>> >>>>> Species >>>>> Singleton >>>>> class HashMap<any K, any V> { >>>>> private static class Keys<any K> { >>>>> species Set<K> allKeys = ... >>>>> } >>>>> >>>>> private static class Vals<any V> { >>>>> species Set<V> allVals = ... >>>>> } >>>>> >>>>> void put(K k, V v) { >>>>> Keys<K>.allKeys.add(k); >>>>> Vals<V>.allVals.add(v); >>>>> } >>>>> } >>>>> class HashMap<any K, any V> { >>>>> private singleton Keys<any K> { >>>>> Set<K> allKeys = ... >>>>> } >>>>> >>>>> private singleton Vals<any V> { >>>>> Set<V> allVals = ... >>>>> } >>>>> >>>>> void put(K k, V v) { >>>>> Keys<K>.allKeys.add(k); >>>>> Vals<V>.allVals.add(v); >>>>> } >>>>> } >>>>> >>>>> But, it doesn't really shine that much; the left is not really much worse >>>>> than the right, just a little more fussy. >>>>> >>>>> In cases where the singleton approach is more natural, the corresponding >>>>> "species in static class" idiom isn't so bad either. But in cases where >>>>> the species approach is more natural, there's something unappealing about >>>>> creating classes (both in source and runtime footprint) in cases 2/3/4 >>>>> when we don't need one. The only place where the singleton approach >>>>> seems to win big is when there are multiple variables in the same scope >>>>> bound by invariants -- here, the singleton >>>>> having a ctor is a big win -- but how often does this happen? >>>>> >>>>> >>>>> So our conclusion is that the species-placement is as good or better for >>>>> the identified use cases -- and it also fits cleanly into the existing >>>>> model for member placement. >>>> >>> >> >
