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.    


Reply via email to