Records -- current status

Brian Goetz brian.goetz at oracle.com
Tue Mar 20 14:35:42 UTC 2018


> In the linked document (
> http://cr.openjdk.java.net/~briangoetz/amber/datum.html), it says that if a
> concrete record extends an abstract record, the state vector of the
> abstract record must be a prefix of the state vector of the concrete
> record.

Correct.

> So, my question is why Base(x, y) and why not i.e. Base(y, z) or Base (x,
> z) or even Base (y, x), etc?

The motivation is to suggest "width subtyping" 
(https://en.wikipedia.org/wiki/Subtyping#Width_and_depth_subtyping); 
while there is no reason we couldn't do as you suggest, the more 
constrained variant guides users towards the sorts of subtyping that 
make sense, which is, "this record subsumes that record."  And if it 
does, it is clearer if its all in one place.

I think users are going to have a hard time reasoning about the boundary 
of when to use a record vs when they have to "fall back to" a class 
(and, the fact that records get the pretty syntax will unfortunately 
multiply this.)  The clearer the boundaries and restrictions, the easier 
it is to form a mental model of what records are for, which is: "it's 
just data".

>
> 1. What about automatically providing a copy constructor for records?

This starts to press on a more general point, which is: what if I want a 
record, all of whose fields are the same as another record, but with one 
changed?  This is the equivalent of "setter" for immutable objects, 
sometimes called a "wither".  One way to get to withers is the same way 
we get to setters and getters; another is to treat it as a form of 
constructor where, if some fields are not specified, they are copied 
from another instance.  (C# supports this through the combination of 
named parameters, default values, and "receiver-defaults.")  This is 
something we're thinking about, especially as we approach value types, 
but don't have a clear story on yet.

> 2. If providing a built-in copy constructor is not desired for all records
> (after all, not every record needs to be copied), it would be very useful
> to have a succint way to express the need of a copy constructor. For
> example:
>
> Instead of writing:
>
>      record Point(int x, int y) {
>          public Point(Point p) {
>              default.this(p.x, p.y);
>          }
>      }
>
> We could only write:
>
>      record Point(int x, int y) {
>          public Point(Point p);
>      }

Yeah, that's cute.  And possibly same for factories (if there's a 
factory that exposes the same signature as a constructor, infer the 
obvious constructor body.)

> I think that the built-in implementation of copy constructors should
> perform a shallow copy.

I think this is the only think we could realistically do.  If you want 
deeper, provide it yourself (and same for clone.)

>
> Why caching the result of first + " " + last in an additional firstAndLast
> instance field? For records, it should be enough to expose derived state
> via accessor methods.

My hope is that this is good enough for V1.  Yes, it gives up some 
optimization opportunities.  (OTOH, developers tend to significantly 
over-value optimization, and if they are so obsessed with performance, 
than maybe what they have is not a record, but a class.  And, just like 
bad programmers will gleefully use one-element arrays to "fool" the 
"dumb" compiler's rules about mutable capture in lambdas, some will take 
the same glee in outsmarting us with a static HashMap.  (The thing that 
makes them bad is not the trick, but the glee.  If you feel clever doing 
this, you're doing it wrong.))

>
> I love final fields by default, I think that finality should be encouraged
> by the language. However, I find it very unpleasant to use a keyword (or a
> reserved word, reserved type name, etc) to change a field's default
> finality. Maybe I'm asking for an impossible here, but isn't there a
> mechanism similar to effectively-final variables, to detect whether a final
> field is being mutated? Certainly, the compiler is able to detect these
> cases, such as when an effectively-final variable is being modified inside
> a lambda.

Yeah, that's asking too much :)  Fields can be public (in which case you 
can't find all the writes at compile time); they can be modified 
reflectively; etc.  I think its reasonable to ask people to be explicit 
here.

> With regards to accessibility, I think that all fields should be public.
> Yes, public! But only for reading purposes (fields should only be
> reassigned from within setter/mutator methods).

Heh, we explored both of these ideas for a while -- both public, and a 
variant public-read, private-write.  (If they were always final, then 
public final would be a credible option, but only if there were no 
chance we'd ever relax finality.)  But, I don't think think Java 
developers would ever accept it.  I'd much rather position records as 
"like the classes you've been writing all along, but less annoying."

> Last but not least, I think it's a de-facto pseudo-standard to use
> fieldName() to return Optional<TypeOfField> for single instance fields.

I don't believe this is remotely as prevalent as you're suggesting 
(though there are surely regional dialects).  Yes, its becoming more 
common to name accessors without the silly "get" prefix, which I 
support.  And, some accessors choose to normalize the type, and one such 
normalization is wrapping with Optional.  But these are two separate 
issues.

>
> Absolutely agree. If you want to provide a custom implementation for
> equals/hashCode, just use a class. Besides, this opens the possibility of
> hash code randomization for records, i.e. to use some seed dependent on the
> current run of the JVM to be used as the base to calculate hash codes
> (something akin JDK9's immutable sets and maps).

We also have plans to make equals/hashCode easier to write, so that you 
don't have to fall all the way down the syntactic cliff to downgrade 
from records to classes.  This should make the landing less unpleasant.

>
> I've never used neither the Clonable interface nor the .clone() method in
> my life.

I use clone() for arrays 100% of the time when I build APIs that use 
arrays.  In the constructor, you can just say

     this.array = array.clone()

and in the read accessor you can say

     return array.clone()

It's really convenient, safe, and 100% clear what's going on.




More information about the amber-dev mailing list