Feedback for records: Accessors name() vs. getName()
Tagir Valeev
amaembo at gmail.com
Wed Aug 5 16:11:52 UTC 2020
Hello!
I mostly agree with Brian: all runtime libraries/frameworks should be
updated for records, and this will be really simple work. But I'm not
sure that languages like Kotlin or Groovy that define the types
statically should be updated. This would add a number of quirks and
ambiguous situations. E.g. consider the following Java code:
interface Intf {
default String getValue() {
return "from interface";
}
}
record MyRec(String value) implements Intf {}
Now, Kotlin code:
fun main(args: Array<String>) {
val r = MyRec("rec");
println(r.value)
}
Which method should be linked here, getValue() from the interface or
value() from the record? Suppose we prefer record accessors if we are
dealing with records. How about this method?
private fun processIntf(intf: Intf) {
if (intf is MyRec) {
println(intf.value) // flow-typing works: intf type is MyRec
here, so we link to value() method
} else {
println(intf.value) // not MyRec, link to getValue() method
}
}
Looks weird.
In general, as there are no setters (thus property cannot be l-value)
and we have no get-prefix, treating record accessors as properties
will only save two characters. This negligible benefit is outweighed
by added confusion, language complexity, and strange compatibility
problems. So I don't know what Kotlin team thinks about this but I
would vote to avoid any specific support here. Well, one thing that
looks good for records is automatic deconstruction. As Kotlin already
has a deconstruction syntax, extending it to Java records looks quite
natural and should not cause big problems.
With best regards,
Tagir Valeev.
On Wed, Aug 5, 2020 at 8:41 PM Brian Goetz <brian.goetz at oracle.com> wrote:
>
> Thanks for the feedback. Some comments inline.
>
> The good news is that most of your concerns are transient; frameworks
> (and there are only a few of them) will have to update to recognize the
> new conventions (and it's easy for them to do so.) We expect, within a
> year of or so of records coming out, that most frameworks will have
> figured this out. One of the reasons to do preview features is so that
> frameworks can see these things coming; I expect, for example, that
> Spring is likely to be record-ready by the time records are a permanent
> feature of the language.
>
> > 1. java.beans.Introspector not recognizing accessors
>
> This one was raised on this list before; there's no reason Introspector
> can't learn about records -- all the reflective machinery is there to do
> so. Someone at one point offered to contribute this change, but so far
> I haven't seen it. We would be happy to take back such a patch.
>
> > I was able to find this email (
> > https://mail.openjdk.java.net/pipermail/amber-dev/2020-January/005479.html)
> > regarding the same issue but the only thing I could see was a proposal to
> > shoehorn Introspector to recognize records instead of making records
> > standards-compliant and not having changing anything in the APIs above
> > java.lang.
>
> Most of the arguments about accessor naming seem to stem from the same
> bit of wishful thinking -- that the getX/setX (and don't forget about
> isX) convention is somehow some sort of "standard" (if it were, it would
> be a terrible one -- we tend to forget how much pain frameworks go
> through to try to recover design intent from naming conventions, because
> most of this code was written 22 years ago.) But, this is simply not the
> case; it is merely a convention. As language stewards, we have to
> balance what is good for the short- and long-term benefit of the
> ecosystem. We surely did consider whether it was worth adopting this
> convention for the sake of short-term migration, but in the end, we
> decided that would be "doing the wrong thing" for the long term.
>
> As I said, most of these have a relatively short-term solution -- we
> just have to remind maintainers of frameworks that the language has
> evolved, and that they need to do some work to evolve with it. And it's
> not a lot of work.
>
> > 2. Unified expression language (EL) in JSP, JSF
> > --------------------------------------------------------------
> > Web expression language will look quite weird as well (address() method
> > versus customer and city properties):
>
> Again, there's no reason why EL can't treat record component accessors
> the same way as methods who happen to be named g-e-t-something. And
> there's no reason why the () would be needed; it's just that they're
> not caught up with the language yet.
>
> We encourage you to request that the maintainers of EL add this to their
> bag of magic tricks; it's not hard.
>
> > 3. Spring Framework hacks, Spring (SpEL)
>
> Same story. I'll bet, though, that Spring is already on top of this.
> > 3. Groovy inconsistency
> > -----------------------
> > You can access getName() getters in Groovy using short syntax:
>
> Same.
>
> > 4. Kotlin inconsistency
> > -----------------------
> > The same as in Groovy also applies to Kotlin:
>
> Same, and again, I'll bet they're already on top of it.
>
> > 5. All libraries will have to be adapted
> > ----------------------------------------
> > There are a lot of libraries that depend on the get/set convention and you
> > would prevent their usage for records. Such as BeanTableModel (swing
> > TableModel implementation), Jackson JSON parser and many others.
>
> Yes, there will be some cost for the ecosystem to adapt -- but I think
> its less than you're making it out to be -- and less about the naming
> that you might think. There are plenty of these libraries, but it's not
> thousands -- I'd bet a few tens would probably cover 97% of uses. And
> the cost for any individual library is literally a few hours work (its
> actually less code to find that a class is a record and that x() is a
> component accessor than, say, to determine that setX() and getX() form a
> matched pair.) This code is, essentially:
>
> if (clazz.isRecord()) {
> Stream.of(clazz.getRecordComponents())
> .forEach(c -> addReadOnlyAccessor(clazz,
> c.getName(), c.getAccessor()));
> }
>
> where `addReadOnlyAccessor` is whatever you do to add the mapping that
> "component N of class C is accessed by method M". All these frameworks
> work by building a "symbol table" indexed by receiver class and
> component name, and map to some description that bottoms out in a
> java.lang.Method (which is what RecordComponent::getAccessor returns.)
> This is much less work than walking inheritance chains to find methods
> that conform to a set of ad-hoc naming conventions, and correlating
> similarly-named methods to infer the presence of a synthetic property.
>
> Further, more of the cost here is not about the naming convention, but
> the fact that frameworks that reconstruct objects reflectively are used
> to calling a no-arg constructor, and then calling setters. That's not an
> option with records, regardless of the naming convention chosen for
> accessors. So Jackson is going to have to adapt no matter what -- and
> the adaptation to immutable objects is far more work than the adaptation
> to a new naming scheme (which has reflection support, see above.)
>
> > 6. Impossibility to retrofit existing record-like classes as records
>
> This one is different from the others, as it speaks not to "it will take
> a little time for the ecosystem to catch up to the language", but about
> what ordinary users have to do. And, this speaks to a design tension we
> wrestled with quite a bit.
>
> The tension is, basically, old code vs new. Obviously, if its possible,
> we want for a new feature to work great for both old AND new code. But
> where there is a conflict between "the way things have been done", and
> how they might be done better in the future, there is a tension.
>
> In records, we made the deliberate choice to design the feature for new
> code than for catering to the quirks of existing code. (With something
> as quirky as the JavaBean naming conventions, we could never be
> quirk-for-quirk compatible anyway; consider the getX vs isX issue, which
> is inconsistently applied throughout the ecosystem, because, well, it's
> a convention, not a standard.) The model for records (immutable,
> transparent carriers for a fixed state vector) is different enough from
> mutable JavaBeans that, even if we had made this concession to legacy, I
> don't think people would have been much happier.
>
> Also, the claim that it is "impossible" to retrofit existing record-like
> classes is vastly overstated. If you have a class that meets the
> requirements to be a record (representation == construction protocol ==
> access protocol == equals/hashCode/toString protocols), you can surely
> migrate it to a record, and then, if you have to for legacy reasons,
> declare some old-style getters to delegate to the new ones. That is far
> from "impossible" -- and was considered in the design (same for having
> additional constructors.) I think what you mean is that then you don't
> get to write a one-line class declaration? And that's true -- because
> you've got a dependency on a framework that uses information from
> outside the type system to infer the semantics of your class. So you
> pay a few-line "tax" for migrating these classes to records. So yes,
> new code will pay less tax than old code, but its not the case you can't
> migrate to records and get some benefit. It's just that if you have
> extra requirements, you have to write some extra code. (Further, even
> this is likely to be transient; once frameworks catch up, the need for
> these "bridge" accessors would go away, and you can delete them.)
>
> I realize its easy to jump from "we've been doing it this way for 22
> years" to "this is the One True Absolutely Right Way To Do It Forever"
> without realizing it, but the language has a responsibility to look
> forward as well as backward, and balance the needs of existing code with
> the needs of new code. Taking a bad library naming convention and
> burning it into the language forever would have been the worse choice.
>
> Cheers,
> -Brian
>
More information about the amber-dev
mailing list