As one of the voices demanding we allow ancillary fields, I can confirm that I had only these derived-state use cases in mind. I don't see anything else as legitimate. That is, I think that the semantic invariants you're trying to preserve for records are worth fighting for, and additional *non-derived* state would violate them.
On Fri, Apr 13, 2018 at 9:46 AM, Brian Goetz <brian.go...@oracle.com> wrote: > Let's see if we can make some progress on the elephant in the room -- > ancillary fields. Several have expressed the concern that without the > ability to declare some additional instance state, the feature will be too > limited. > > The argument in favor of additional fields is the obvious one; more > classes can be records. And there are some arguably valid use cases for > additional fields that don't conflict with the design center for records. > The best example is derived state: > > - When a field is a cached property derived from the record state (such > as how String caches its hashCode) > > Arguably, if a field is derived deterministically from immutable record > state, then it is not creating any new record state. This surely seems > within the circle. > > The argument against is more of a slippery-slope one; I believe developers > would like to view this feature through the lens of syntactic boilerplate, > rather than through semantics. If we let them, they would surely and > routinely do the following: > > record A(int a, int b) { > private int c; > > public A(int a, int b, int c) { > this(a, b); > this.c = c; > } > > public boolean equals(Object other) { > return default.equals(other) && ((A) other).c == c; > } > } > > Here, `c` is surely part of the state of `A`. And, they wouldn't even > know what they'd lost; they would just assume records are a way of > "kickstarting" a class declaration with some public fields, and then you > can mix in whatever private state you want. > > Why is this bad? While "reduced-boilerplate classes" is a valid feature > idea, our design goal for records is much more than that. The semantic > constraints on records are valuable because they yield useful invariants; > that they are "just" their state vector, that they can be freely taken > apart and put back together with no loss of information, and hence can be > freely serialized/marshaled to JSON and back, etc. > > We currently prohibit records like `A` via a number of restrictions: no > additional fields, no override of equals. We don't need all of these > restrictions to achieve the desired goal, but we also can't relax them all > without opening the gate. So we should decide carefully which we want to > relax, as making the wrong choice constrains us in the future. > > Before I dive into details of how we might extend records to support the > case of "cached derived state", I'd like to first come to some agreement > that this covers the use cases that we think fall into the "legitimate" > uses of additional fields. > > > > On 3/16/2018 2:55 PM, Brian Goetz wrote: > >> There are a number of potentially open details on the design for >> records. My inclination is to start with the simplest thing that preserves >> the flexibility and expectations we want, and consider opening up later as >> necessary. >> >> One of the biggest issues, which Kevin raised as a must-address issue, is >> having sufficient support for precondition validation. Without foreclosing >> on the ability to do more later with declarative guards, I think the recent >> construction proposal meets the requirement for lightweight enforcement >> with minimal or no duplication. I'm hopeful that this bit is "there". >> >> Our goal all along has been to define records as being “just macros” for >> a finer-grained set of features. Some of these are motivated by >> boilerplate; some are motivated by semantics (coupling semantics of API >> elements to state.) In general, records will get there first, and then >> ordinary classes will get the more general feature, but the default answer >> for "can you relax records, so I can use it in this case that almost but >> doesn't quite fit" should be "no, but there will probably be a feature >> coming that makes that class simpler, wait for that." >> >> >> Some other open issues (please see my writeup at >> http://cr.openjdk.java.net/~briangoetz/amber/datum.html for reference), >> and my current thoughts on these, are outlined below. Comments welcome! >> >> - Extension. The proposal outlines a notion of abstract record, which >> provides a "width subtyped" hierarchy. Some have questioned whether this >> carries its weight, especially given how Scala doesn't support case-to-case >> extension (some see this as a bug, others as an existence proof.) Records >> can implement interfaces. >> >> - Concrete records are final. Relaxing this adds complexity to the >> equality story; I'm not seeing good reasons to do so. >> >> - Additional constructors. I don't see any reason why additional >> constructors are problematic, especially if they are constrained to >> delegate to the default constructor (which in turn is made far simpler if >> there can be statements ahead of the this() call.) Users may find the lack >> of additional constructors to be an arbitrary limitation (and they'd >> probably be right.) >> >> - Static fields. Static fields seem harmless. >> >> - Additional instance fields. These are a much bigger concern. While >> the primary arguments against them are of the "slippery slope" variety, I >> still have deep misgivings about supporting unrestricted non-principal >> instance fields, and I also haven't found a reasonable set of restrictions >> that makes this less risky. I'd like to keep looking for a better story >> here, before just caving on this, as I worry doing so will end up biting us >> in the back. >> >> - Mutability and accessibility. I'd like to propose an odd choice here, >> which is: fields are final and package (protected for abstract records) by >> default, but finality can be explicitly opted out of (non-final) and >> accessibility can be explicitly widened (public). >> >> - Accessors. Perhaps the most controversial aspect is that records are >> inherently transparent to read; if something wants to truly encapsulate >> state, it's not a record. Records will eventually have pattern >> deconstructors, which will expose their state, so we should go out of the >> gate with the equivalent. The obvious choice is to expose read accessors >> automatically. (These will not be named getXxx; we are not burning the >> ill-advised Javabean naming conventions into the language, no matter how >> much people think it already is.) The obvious naming choice for these >> accessors is fieldName(). No provision for write accessors; that's >> bring-your-own. >> >> - Core methods. Records will get equals, hashCode, and toString. >> There's a good argument for making equals/hashCode final (so they can't be >> explicitly redeclared); this gives us stronger preservation of the data >> invariants that allow us to safely and mechanically snapshot / serialize / >> marshal (we'd definitely want this if we ever allowed additional instance >> fields.) No reason to suppress override of toString, though. Records could >> be safely made cloneable() with automatic support too (like arrays), but >> not clear if this is worth it (its darn useful for arrays, though.) I >> think the auto-generated getters should be final too; this leaves arrays as >> second-class components, but I am not sure that bothers me. >> >> >> >> >> >> > -- Kevin Bourrillion | Java Librarian | Google, Inc. | kev...@google.com