On Jan 8, 2020, at 3:55 PM, Brian Goetz <brian.go...@oracle.com> wrote: > > We're gathering a list of what non-bugfix things we want to do for the second > preview of records. So far, on my list, I have: > > 1. Revisiting the j.l.Record supertype. We want to support inline records > when we have inline types. Until now, we've been working on the assumption > in Valhalla that inline classes can only extend interfaces, not abstract > classes, so it was suggested that Record should be an interface. However, > that topic in Valhalla is under reconsideration, and I want to wait until > that discussion plays out before making any changes here. > > It has also been pointed out that the name Record is somewhat clash-y. I'm > not really willing to pick a lousy name just to reduce the chance of clashes, > but I might be OK with a name like RecordClass. (Bikeshed alert: if you want > to discuss any of these topics, please start a new thread; this one is about > curating a to-do list.) > > 2. Accessibility of mandated members. Remi noted that the requirement that > the mandated record members be public, even for non-public classes, was > weird. But, at the time, the spec was in a state that trying to revisit this > was impractical -- Gavin has now left the spec in a much cleaner place, and > so it is reasonable to reopen this discussion. The leading alternate > candidate is to propagate the accessibility of the record to its mandated > members (public for public, etc), but still require the author to say what > they mean.
+1 on going another round on accessibility. I specifically want to make sure we are doing the best thing on access defaults (what happens when you don’t mention *any* access mode). I suppose that’s a separate item, related to this one. Even though we have a decision of record on this, I’m calling it out here and now because we are stuck with the final decision we make this time around. (Maybe like a mandatory appeal for a capital case.) > 3. Nesting considerations. In 14 we will fix the issues surrounding local > records, but we still ban nested records in non-static classes. We should > fix this -- by dropping the restriction on static members in inner classes -- > and then bring records, enums, and interfaces to parity (allowing local and > nested flavors of all, all implicitly static.) Agreed. It’s time to let static types (and maybe other statics) nest more freely. (The reasons for the current restrictions are IMO obsolete, because we will never take the path of making inner classes into true dependent types. Back in the day we thought we would leave that door open a crack. If perchance we do dependent types in the future, a la Beta and Scala, we’ll surely declare them explicitly.) > 4. Abstract records. Several people have asked "what about abstract > records"; while these are theoretically possible, there are some > complications that I think are best left for treating these as a later > addition if needed. But, for the record, here are some thoughts from the > last time I looked into this. > > Given that records are nominal tuples, the notion of "width subtyping" comes > immediately to mind. So, let's go with that for a moment: you could say > > abstract record A(int a, int b) { } > and > record B(int a, int b, int c) extends A { } > > and there is a natural width subtyping relationship. We don't have problems > with the equality contract because the abstract class leaves equals() > abstract. > > But, this is a story that is likely to not be entirely satisfactory. Do we > require that the state of A form a prefix for the state of B? This may not > be the API people want. Do we require that super(a,b) be passed through > unchanged? The constraints on the subclass in this model get, constraining. > > What if there were a more flexible relationship: > > record B(int a, int b, int c) extends A(a, b) { } > > Now, there's more flexibility, at the cost of a new "extends" construct. And > what if you want to fix some field of A as a constant: > > record iload2_bytecode() extends bytecode(0x1c) { } > > These all seems like reasonable things to want when you get into abstract > records ... but they all start to push on the "records imperative". So for > now, we're doing nothing, until we have a better story for what we actually > want to do. Personally, I don’t have much appetite for abstract records, any more than I do for the notion of tuple inheritance. (int x, int y) is not a subtype of (int x). Remi said it well: Inheritance is not the right mechanism to express such things. *But.* I *do not* want this decision to accidentally drive the translation strategy. Just because we don’t have an abstract class to plant “toString” in doesn’t mean we are dispensed from defining a modular way to manage “toString” on records. We might start with a hardwired public API for this, but I think we also want to keep an eye on Brian’s dreams for wiring up implementations by delegation. Where I think this goes is towards a record implementation class with a public API which (a) works well today and (b) is likely to be the starting point of a more pluggable story for behaviorally polymorphic APIs like the toString of record. (And the equals and hashCode.) The libraries, not the language, should define these methods, and the shape of the definition should be future-friendly. > 5. Deconstruction patterns. Yes, records should be deconstructible with > deconstruction patterns. > > Anything else, that we've already discussed that I left out? 6. Default access modes in records. (See above. ) Choices: Package scope like classes, public like interfaces, something new for records. 7. Translation strategy. (Is it polished and future-friendly?) In the review it’s done by means of jlr.ObjectMethods::bootstrap, which seems a little non-specific to records. Doing something with a simple-enough indy (and no other javac-generated code!) is probably future-friendly. When and if we get “mindy” (indy-flavored method definitions) the indy instruction can be quietly retired in favor of a mindy-bootstrap, with no semantic change. Beyond that, the happy point I want to get to in the future is a sort of inherited mindy, where javac doesn’t mention “toString” at all in any give record classfile, and instead there’s some wiring somewhere that says, “records conform to Object via this mindy provider”. That verges on an abstract class or even an interface; do we need or want to add something like that as a mandated feature of all records? Maybe not. But I do think we want to get a foot into the door somehow with records, today, that we parley into a channel for new features tomorrow. 8. Transactional methods (aka. reconstructors): Consider generalizing existing patterns that support multi-component data processing. This can be left for the future, but it’s worth a look now in order to make sure today’s similar forms (which are canonical constructors and deconstruction methods) don’t accidentally tie our hands. What’s a “transactional” method (or expression or lambda)? OK, I just made up the term, and I’ll have a different one tomorrow, but the concept has been on the table for a long time. In essence there’s a set of names (record component names) which are put in scope at the top of a block, may be sequentially modified by the block, and are committed at the bottom. I’ll punt the rest of the discussion to a separate email. 9. If we don’t decide hashCode explicitly, I think we’ll back ourselves into a bad default decision. Should the hashing logic for r.hashCode() be specified in a portable manner? I say, “no”, since there’s no good portable solution. Objects.hash is portable, but other than that is a poor algorithm. I’ll fork a separate thread for this. Right now we don’t *specify* that we combine hashes using the base-31 polynomial, but we *do* use that stupid old thing. This is plausibly conservative but it will get it into a worse spot than necessary. We can buy ourselves some time by putting weasel words into Record::hashCode (or its successor) and ObjectMethods::bootstrap. We can promise that the hashCode result depends (typically) on all components of the record, and on nothing else. We should also explicitly state that the method for combining the bits is subject to change, very much like we specify that the iteration order for Set.of and Map.of is subject to change, like this: http://hg.openjdk.java.net/jdk/jdk/file/e9c11ba790b2/src/java.base/share/classes/java/util/Set.java#l83 Finally, for luck, we should fold a dash of salt into the hash function to keep users on their toes, like this: http://hg.openjdk.java.net/jdk/jdk/file/e9c11ba790b2/src/java.base/share/classes/java/util/ImmutableCollections.java#l619 If we aren’t cautious about this, users will eventually discover that we are using the Objects.hash algorithm, we will document it, and then we’ll be locked out from an easy improvement to records. I don’t think we should even promise that the record hashCode is mixed from the hashCodes of the components. That’s TMI, and it boxes us in. (I want a vectorized hashCode someday for records and inlines.) All we need to do is promise that a record type’s hashCode fulfills the Object contract, especially relative to the record’s equals method. No other promises, other than that the hashCode is subject to change. And then rub some salt on it to change it. Those are my remaining concerns. — John