The second of the two topics raised by Kevin’s note. The subject of ancillary fields seems to be the hardest question to answer; none of the answers seem great. But let’s tease apart some of the use cases.
I think Kevin’s comment here is, at root: “but derived fields are super-useful, and seem perfectly safe, isn’t there some way to wedge these into the model?” Correct me if I’m wrong, but I would think you would agree that arbitrary mutable fields in records are _not_ a great idea. So the game here is, is is possible to carve out a space where derived fields are possible, but non-derived ones are not? (And, I think there is, if we stop thinking about “fields” and start thinking about semantics. Derived fields are a mechanism, in aid of: is it possible to ensure that a computation on record state is done at-most-once? More on that later.) > >> >> I still want to understand what the scenario we're worried about here is. >> Whether the value is computed later using a "lazy fields" feature or eagerly >> in the constructor, only the record's state is in scope, and sure, people >> can shoot themselves in the foot by calling out to some static method and >> getting some result not determined by the parameters, but why is this worth >> worrying about? Do you have an example that's both dangerous and tempting? >> (Sorry if you've said it before.) > > If we did allow them, the next thing people (Alan already did, and you made > this same comment in an earlier round) would ask is whether they can be > mutable — so that derived fields can be lazily derived, Now, records are a > combination of a “true record" plus an unconstrained bag of mutable state. > > My question did actually exclude this option. We should tell those people no. OK, so you’re suggesting: ancillary final fields with initializers is OK. Not a totally silly option (we did talk about it before), but let’s look at how it affects the user perception of what the feature is for, and then try and make a cost-benefit comparison between this and the base case (no additional fields.) Note that the benefit we’re aiming for here is purely an optimization; the avoidance of recalculating derived state. (Nagging question: if this optimization _is_ super-important, is this the best way to ensure it?) I think you have also noted in the past that there are lots of ways to get around the restriction, at various degrees of obviously-missing-the-point: final notReallyDerived = new Foo[1]; // effectively, a mutable Foo field static final WeakHashMap<MyRecord, Foo> // same effect, just more absurd > > And what do you think the chances are that this state won’t make it into > equals/hashCode semantics? > > If it's derived, it doesn't hurt that much; if it's not, why are they working > so hard to not make it a regular record field? > This is part of what I'm talking about when I say "sure, they can shoot > themselves in the foot". What is a realistic example we are worried about? The language doesn’t have a notion of “derived quantity”, so any attempt to restrict the relaxation to derived quantities will be an approximation. But I think the “why are they working so hard” question answers itself: concision! And the most complex the boundary of the records feature is, the more that Billy will be confused into thinking this is just an overly-complicated, sharp-edged, frankly-crappy way to get concision. If we want to carve out an exception for “derived state”, let’s go there more directly (in a way that benefits all classes, not just records). One way we discussed was something like this: lazy final String fullName = first + last; and then allowing records to have lazy fields. This is a stronger hint that this is no ordinary field, but ultimately still is too easy to abuse by initializing it with a one-element array. A more direct way to get there is to introduce a semantic notion that a computation should be done at most once: record Name(String first, String last) { __at_most_once String fullName() -> first + last; } This has a lot of advantages over the field approach: - We have said directly what we mean, in a way that doesn’t “leak” its implementation mechanism (fields); - The runtime can probably optimize more directly and flexibly; - We haven’t distorted the set of class members for a performance concern; - Mechanism usable equally by records and non-records. A downside is that we don’t have this mechanism yet, and we don’t really want to hold up records to get it.