Hi Brian,

thank you for your work on this and this wonderful writeup.
After working both as Scala developer and Scala compiler developer those
design areas are very pleasant to
reiterate and try to think when\if Scala approach will work well in Java.

I agree with all that you wrote and want to contribute several observations:

> Records could be safely made cloneable() with automatic support too, but
not clear if this is worth it

My experience suggests that people rarely want to close case classes in
Scala.
This is likely because, we provide a bunch of methods with default
arguments called `copy`.
You can write something like

case class A(i: Int, d: Double)

val myA = A(1, 2).copy(d = 2)


Fields that you don't specify are shallow copied from original one.

I'm unsure if you want or can provide similar api but it was very useful in
Scala.

> Extension

Do I understand it right that you are proposing that Records cannot inherit
normal classes, while abstract records can be inherited by normal classes?

> Some have questioned whether this carries its weight, especially given
how Scala doesn't support case-to-case extension.
To give some perspective, over years I started to love this limitation. It
steers towards a better design where Data does not extend other Data.

One area that I want to draw your attention to is code evolution.

Due to subtle things in Scala case classes handling, there are some
situations
when it wasn't possible to replace a case class with seemingly equivalent
user written code.
While this wasn't an original intention, such implementation artifacts
aren't as rare as we wished
as Scala compiler grew a lot of behavior specific to case classes.
This behavior work unreliably or does not work at all in case you inherit a
case class.

Is a decision to make class be a record a binding decision over long term?
Given that records cannot inherit from normal classes, but normal classes
can inherit from records AND
names are important part of source level compatibility, I'm torn on
abstract records.
They are the only proper inheritance available for records in your design,
which I feel is a compatibility trap.
In case one day someone will need to add something that is not supported by
records to what used to be
an abstract record, they will have to convert the entire hierarchy
including all the children.
Sometimes this is infeasible.

Thus if you have abstract records that can be inherited, I feel that there
might be a need for records to be able to
inherit arbitrary classes, and this makes it much harder to provide safe
records.

Thus I'd like to also suggest to not include abstract records in first
implementations.

Best,
Dmitry

On Fri, Mar 16, 2018 at 11:55 AM, Brian Goetz <brian.go...@oracle.com>
wrote:

> There are a number of potentially open details on the design for records.
> My inclination is to start with the simplest thing that preserves the
> flexibility and expectations we want, and consider opening up later as
> necessary.
>
> One of the biggest issues, which Kevin raised as a must-address issue, is
> having sufficient support for precondition validation. Without foreclosing
> on the ability to do more later with declarative guards, I think the recent
> construction proposal meets the requirement for lightweight enforcement
> with minimal or no duplication.  I'm hopeful that this bit is "there".
>
> Our goal all along has been to define records as being “just macros” for a
> finer-grained set of features.  Some of these are motivated by boilerplate;
> some are motivated by semantics (coupling semantics of API elements to
> state.)  In general, records will get there first, and then ordinary
> classes will get the more general feature, but the default answer for "can
> you relax records, so I can use it in this case that almost but doesn't
> quite fit" should be "no, but there will probably be a feature coming that
> makes that class simpler, wait for that."
>
>
> Some other open issues (please see my writeup at
> http://cr.openjdk.java.net/~briangoetz/amber/datum.html for reference),
> and my current thoughts on these, are outlined below. Comments welcome!
>
>  - Extension.  The proposal outlines a notion of abstract record, which
> provides a "width subtyped" hierarchy.  Some have questioned whether this
> carries its weight, especially given how Scala doesn't support case-to-case
> extension (some see this as a bug, others as an existence proof.)  Records
> can implement interfaces.
>
>  - Concrete records are final.  Relaxing this adds complexity to the
> equality story; I'm not seeing good reasons to do so.
>
>  - Additional constructors.  I don't see any reason why additional
> constructors are problematic, especially if they are constrained to
> delegate to the default constructor (which in turn is made far simpler if
> there can be statements ahead of the this() call.) Users may find the lack
> of additional constructors to be an arbitrary limitation (and they'd
> probably be right.)
>
>  - Static fields.  Static fields seem harmless.
>
>  - Additional instance fields.  These are a much bigger concern. While the
> primary arguments against them are of the "slippery slope" variety, I still
> have deep misgivings about supporting unrestricted non-principal instance
> fields, and I also haven't found a reasonable set of restrictions that
> makes this less risky.  I'd like to keep looking for a better story here,
> before just caving on this, as I worry doing so will end up biting us in
> the back.
>
>  - Mutability and accessibility.  I'd like to propose an odd choice here,
> which is: fields are final and package (protected for abstract records) by
> default, but finality can be explicitly opted out of (non-final) and
> accessibility can be explicitly widened (public).
>
>  - Accessors.  Perhaps the most controversial aspect is that records are
> inherently transparent to read; if something wants to truly encapsulate
> state, it's not a record.  Records will eventually have pattern
> deconstructors, which will expose their state, so we should go out of the
> gate with the equivalent.  The obvious choice is to expose read accessors
> automatically.  (These will not be named getXxx; we are not burning the
> ill-advised Javabean naming conventions into the language, no matter how
> much people think it already is.)  The obvious naming choice for these
> accessors is fieldName().  No provision for write accessors; that's
> bring-your-own.
>
>  - Core methods.  Records will get equals, hashCode, and toString.
> There's a good argument for making equals/hashCode final (so they can't be
> explicitly redeclared); this gives us stronger preservation of the data
> invariants that allow us to safely and mechanically snapshot / serialize /
> marshal (we'd definitely want this if we ever allowed additional instance
> fields.)  No reason to suppress override of toString, though. Records could
> be safely made cloneable() with automatic support too (like arrays), but
> not clear if this is worth it (its darn useful for arrays, though.)  I
> think the auto-generated getters should be final too; this leaves arrays as
> second-class components, but I am not sure that bothers me.
>
>
>
>
>
>

Reply via email to