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

Reply via email to