Data classes

Brian Goetz brian.goetz at oracle.com
Thu Nov 2 13:59:11 UTC 2017



> This is a very good and a pleasant read, thank you!
> Especially nice to see a clear definition of design requirements.
> I have several follow up questions:
>
>  *
>
>     do you envision data classes as Java language feature or JVM
>     platform feature?
>     Are additional features such as externalization implemented by
>     runtime magic or by
>     Bytecode generated by compiler ahead of time?
>

We will likely do the same thing we did with Lambda, where we provide 
JDK runtime for implementing the Object methods using indy. In our 
prototype, we expose invokedynamic bootstraps that implement equals, 
hashCode, and toString, which take as their static arguments method 
handles for fetching the fields (and in the case of toString, also field 
names), like this:

     public static CallSite makeEquals(MethodHandles.Lookup lookup, 
String invName, MethodType invType,
                                       Class<? extends DataClass> 
dataClass, MethodHandle... getters) throws Throwable {
         return new ConstantCallSite(makeEquals(dataClass, 
List.of(getters)));
     }

The static compiler, in the body of equals(), just generates a a single 
invokedynamic instruction whose bootstrap is this method, whose static 
arguments describe this class, and whose dynamic arguments are those of 
equals().  Other language compilers could do the same.

>  *
>
>     requirements stipulated in section “Towards requirements for data
>     classes” feel like they
>     should not only impose limits on the dataclass, but also types of
>     fields the data class could contain. For example:
>     |For any instance c of C, ctor(dtor(c)) equals c, according to the
>     equals() contract for C,
>     and further, that the composition ctor(dtor(x)) is an identity on
>     the codomain of ctor.|
>     this assumes that equals on fields is also well-behaved.
>     While in perfect world I’d prefer to limit data-classes to only
>     have well-behaved fields,
>     I see that this may be infeasible. I think it is still worth
>     spelling that requirements&definitions
>     of transparent carrier are conditional on well-behaveness of
>     fields + formally defining this well-behaveness.
>

Yeah, I'm not really sure what more we can do here, but I agree that 
this is a place where our requirements could be implicitly undermined.  
Any suggestions?

>  *
>
>     while I agree that names are meaningful in Java, it would be nice
>     to be able to generically inspect and\or
>     deconstruct arbitrary data-class as if it was a tuple. This will
>     allow generic handling of data-classes
>     when it makes sense. Do you have ideas in this area?
>

We will generate deconstructor patterns for these things, of course, 
which can be statically invoked by name (case Point(var x, var y)). And 
deconstructor patterns can be invoked reflectively, but I know that's 
also not what you mean (though may be good enough for frameworks).  I 
think what you mean is something more like "data interfaces", where a 
data class can "implement" a more abstract deconstructor?

>  *
>
>     Mutability: I agree that enforcing complete immutability is
>     impractical.
>     But I’m curios if you’re current thinking is to limit mutability
>     to class itself(as if setters were private)
>     or to allow data classes to be mutated externally(as if setters
>     were public).
>     My feeling that the former route is nice because it allows data
>     class authors
>     to expose mutability only if only they want to. What was your
>     thinking?
>

The most restrictive position is: fields are always final and are always 
publicly readable (either because the fields themselves are public, or 
we provide public read accessors.)  This is a principled position, but 
is probably impractical.

The likely practical compromise is:
  - fields are final by default, but can be declared "unfinal"
  - fields are private by default, but can be declared public
  - you get public read accessors, no matter what

So if you do nothing, you get final and readable, which is a reasonable 
default.  If you opt into mutability, fields are still private, and you 
have the option to expose mutative methods, or not.  If you opt into 
mutability and public-ness, people can do whatever they want.


>  *
>
>         The arguments to the extends Base() clause is a list of names
>         of state components of Sub …
>         must be a prefix of the state description of Sub,
>
>     Curiosity: why do you need it to be a prefix? It didn’t understand
>     it from the writeup.
>

Essentially, this is "nominal width subtyping".

This isn't essential, though I think this requirement is useful to nudge 
people away from mistaken ideas about extension; the degrees of freedom 
it removes are relatively weak in expressiveness, but still make it 
possible for users to confuse themselves, such as when Foo(int x, int y) 
is declared to extend Bar(int y, int x).

>  *
>
>     I feel like this might be limiting: if you’re working on abstract
>     ADTs, you are likely to work on
>     Very abstract entities, where convenient order of arguments is not
>     yet clear. When ADTs become less abstract
>     in different subtypes it will become clear and can be different in
>     different branches of inheritance hierarchy.
>

Please share examples!

Cheers,
-Brian

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20171102/1b7e7381/attachment.html>


More information about the amber-spec-experts mailing list