Fwd: JDK 14 Preview Records constructors

interlink.sg7 at gmail.com interlink.sg7 at gmail.com
Tue Jun 16 14:40:14 UTC 2020


Hello,

 

I’ll answer to the the deepest mail branch and gather all the replies here,
hope it gets posted to the correct mailing list.

 

> [Johannes Kuhn] Thanks for sharing this. Highlighted a few things I was
not aware of as EJC doesn't check for that. 
Filled a bug on the eclipse bug tracker[1].

It’s great this discussion had atleast some purpose then :)

 

> [Remi Forax] There is a bit of theory behind the compact constructor, it's
a way to define preconditions [1] in a design by contract way [2], that why
the implicit parameters and implicit field assignments are grouped together.

> [Remi Forax] You can dream :) given that unlike Eiffel the compact
constructor let you write any valid Java statements, being able to surface
them in a meaningful way in the Javadoc is not that easy.

 

I hadn’t looked at the compact constructors that way. Heh, I had the chance
to be thaught Design by Contract and Eiffel by Betrand Meyer in person.
Other then it involved cooking spinach, not much stuck apparently.

 

> [Brian Goetz] These restrictions add up to the requirement that
constructor chains 

should "bottom out" in the canonical constructor.

 

I think most of my confusion stem from not capturing this requirement. I was
looking at each constructor as being a unique, independant entry point into
a Record (as is often the case with Classes).

If I think that way that all constructors need to funnel through the
canonical constructor for invariant checking, it makes more sense.

You could go even a bit further and regard any code after this(
) in a
non-canonical constructor as dead code. You can’t do much after, right?
Except maybe logging and error-prone hacky stuff.

 

> [Brian Goetz] In the compact constructor, you just mutate the parameters
if you want to normalize them (after validation, if any.)

 

I guess this also needs some unlearning. I’m very uncomfortable in mutating
parameters, maybe unnecessarily in this context. There are even people that
write «final» to method parameters.

 

> [Brian Goetz] In your (1), the `Example1(int x, int defaultX)` exists
solely to serve the main constructor; it would be silly to expose this as a
public constructor.

In your (2), this is trivially replacable with constructor chaining,
resulting in far clearer, and less error-prone, code.

 

In both examples I was trying to construct something where it might be of
value to relax the restriction that the canonical constructor must be at the
end of the chain.

In (1) where you might want to reverse the chain because you might want a
style where a custom constructor is the «main» constructor

and in (2) where it would be erroneous to use the canonical constructor
because it would change the result. Here because UUID::nameUUIDFromBytes
generates a new UUID from the given UUID.

Though I concede, these use-cases might just not be really fitting what
Record are meant to be and do.

Here some food for thought however. Lets imagine that Java has default
parameters someday (would be a nice tandem with named innvocation):

 

    record Foo(int x, int y) {

        public Foo(int x, int y, int a = 0, int b = 0) {

            this(x + a, y + b);

        }

    }

 

Looks a bit like recursion / chicken-egg problem to me, but that’s on you
compiler writers to figure out :P

 

> [Brian Goetz]: [
] And once you add one such knob, you will invariably
have a dozen, and now, rather 

than being a language feature with defined semantics, you've stuck a 

crappy macro generator in your language where no one can rely on the 

resulting semantics.

 

Ok sounds very reasonable. Now we could also imagine a singleton Record or
Record’s that only allows a set of predefined instances. Those would only
work if the constructor is private.

Then again we those fall outside of the usecase of Records and we have
Classes and Enums for that.

Though I played around with it as some sort of constant holder / enhanced
enum. It’s really tempting, forgive me:

 

    public record Primitive<X>(Class<X> boxClass, X defaultValue) {

        public static final Primitive<Byte> BYTE = new
Primitive<>(Byte.class, (byte) 0);

        public static final Primitive<Short> SHORT = new
Primitive<>(Short.class, (short) 0);

        public static final Primitive<Integer> INT = new
Primitive<>(Integer.class, 0);

        public static final Primitive<Float> FLOAT = new
Primitive<>(Float.class, 0f);

        public static final Primitive<Long> LONG = new
Primitive<>(Long.class, 0L);

        public static final Primitive<Double> DOUBLE = new
Primitive<>(Double.class, 0d);

        public static final Primitive<Character> CHAR = new
Primitive<>(Character.class, (char) 0);

        public static final Primitive<Boolean> BOOLEAN = new
Primitive<>(Boolean.class, false);

    }

 

> [Remi Forax] Yes, right, it was an issue of the first spec, it's not true
any more with Java 15.

 

This is regarding the static factories. Can you elaborate a bit on why this
is not true anymore with Java 15?

 

> [Brian Goetz] In (4), you say "assignment without <this> is unnatural."
With respect, this is just saying "Augh, the learning, it hurts!" Similarly,
"looks nothing like Java" could be leveled against NEARLY EVERY feature
we've added [
] In Java 10, they said "var x = e" looks like Javascript.
But, how uncomfortable are you with these locutions today?

 

I feared this would be an answer from your side. My standpoint is that so
far Java was following more or less the slogans

"Explicit is better than implicit",  "Readability counts. " and "There
should be one-- and preferably only one --obvious way to do it."

which I stole from Zen of Python PEP20. I feel the canoncal constructor is
breaking this a bit. As for when Generics were introduced, I was too young
for that and lambdas felt nicely integrated into the rest of the language.
The migration was really smooth. With "var x = e" I’m veeeeery
uncomfortable. Actually var was banned in our company per default checkstyle
(ca. 50 devs, on Java 11 since release).

I guess this is the point where you can’t make everyone happy.

 

> [Brian Goetz] Perhaps a better set of questions is: "will we ever get
these goodies for regular classes too?"  We're working on it.

 

You got me. Looking forward to it!

 

Cheers,

 

Simon

 

 

Von: Brian Goetz <brian.goetz at oracle.com <mailto:brian.goetz at oracle.com> > 
Gesendet: Dienstag, 9. Juni 2020 23:18
An: Johannes Kuhn <info at j-kuhn.de <mailto:info at j-kuhn.de> >; amber-dev
<amber-dev at openjdk.java.net <mailto:amber-dev at openjdk.java.net> >
Cc: interlink.sg7 at gmail.com <mailto:interlink.sg7 at gmail.com> 
Betreff: Re: Fwd: JDK 14 Preview Records constructors

 

Yes, the error message should be more specific.  "Another constructor of
class Foo" would do it.  

On 6/9/2020 5:08 PM, Johannes Kuhn wrote:

Thanks for sharing this. 

Highlighted a few things I was not aware of as EJC doesn't check for that. 
Filled a bug on the eclipse bug tracker[1]. 

Playing around with that, I found the following "inconsistency": 

    public record Foo(int bar) { 
        public Foo() { 
            super(); 
            this.bar = 0; 
        } 
    } 

Fails to compile with "constructor is not canonical, so its first statement
must invoke another constructor". 
I wonder what "super();" is? (According to JLS § 8.8.7.1, it's an Explicit
Constructor Invocation) 

Should probably display a different, more specific error message. 
Like "constructor is not canonical, so its first statement must invoke
another constructor of this record". 

(Also, it *might* be useful to have a non-public non-canonical constructor
that doesn't copy an array. But that's a different discussion.) 

- Johannes 

[1]: https://bugs.eclipse.org/bugs/show_bug.cgi?id=564146 

On 09-Jun-20 21:24, Brian Goetz wrote: 

Received on the spec-comments list. 




-------- Forwarded Message -------- 
Subject:     JDK 14 Preview Records constructors 
Date:     Tue, 9 Jun 2020 00:00:09 +0200 
From:     interlink.sg7 at gmail.com <mailto:interlink.sg7 at gmail.com>  
To:     amber-spec-comments at openjdk.java.net
<mailto:amber-spec-comments at openjdk.java.net>  
CC:     james.laskey at oracle.com <mailto:james.laskey at oracle.com>  



Hello, 


I'm not sure if this is the correct place to give feedback on Records, I 
apologise if it isn't. 

I recently played around with the preview Records features from JDK 14 a bit

(mainly JShell). 

There's a few things I'm at odds with and I'd like to highlight only those 
in this post and I hope you can give me some insights that led to certain 
restrictions 

and where I'm misunderstanding some things maybe. 

My main issue is mostly how different constructors work in Records compared 
to Classes and the resulting inconsistency. 

The following examples are a bit constructed and don't make logical sense, 
but I hope my idea gets across :) 


1. Canonical constructor can not call <custom constructor>, e.g. 
delegating to spezialized normalizing constructor with a constant default 
value: 


record Example1(int x) { 

public Example1(int x) { 

this(x, 0); // compile error 

} 


public Example1(int x, int defaultX) { 

this.x = x >= 0 ? x : Math.max(defaultX, 0); 

} 

} 


2. <Custom constructors> must call canonical constructor, e.g. support 
multiple input types or when you can't reuse canonical constructor: 


record Example2(UUID uuid) { 

public Example2 { 

uuid = UUID.nameUUIDFromBytes(uuid.toString().getBytes()); 

} 


public Example2() { // compile error 

this.uuid = UUID.randomUUID(); 

} 


public Example2(String uuid) { // compile error 

this.uuid = UUID.fromString(uuid); 

} 

} 



3. Canonical Constructor must be public, so it's not possible to have 
only static factories or it forces normalization outside of Records 
constructor: 


record Example3(UUID uuid) { 


private Example3 { // compile error 

} 


public Example3(String uuid) { // compile error 

String uuidNormalized = StringUtils.toLowerCase(uuid); 

try { 

this.uuid = UUID.fromString(uuidNormalized); 

catch(IllegalArgumentException e) { 

this.uuid = null; 

} 

} 


public static Example3 fromString(String uuid) { 

return new Example3(uuid); 

} 

} 


4. Assignment without <this> in canonical constructor is very 
unnatural, e.g. use of <this> in Classes is very logical when you would 
otherwise reassign the parameter 

In Records however : 


record Example4(int x) { 

public Example4 { 

x = x + 1; // assigns field, but looks nothing <like Java> 

} 


public Example4(int x, int plus) { 

this(x); // hrrng 

x = x + plus; // reassign parameter 

} 

} 


record Example5(int x) { 

public Example5 { 

this.x = x + 1; // read it might be forbidden in the future :( 

} 


public Example5(int x, int plus) { 

this(x); // hrrng 

this.x = x + plus; // is compile error 

} 

} 


5. It's not possible to define the canonical constructor with the exact 
same parameter list as the Record definition has. 

The simple canonical constructor without parameters should just be an alias 
for that in my opinion. 

For symmetrie reasons (with other constructors) and clearity I would like to

define the canonical constructor with the required parameters. 


I think most of those discrepancies (especially 4) and 5)) come from the 
desire to be less verbose and the definition of records (state and only 
state) and still have some <convenience> like Classes. 

However the limitations and half-way implicit <magic> makes it really 
confusing. I think it would have been better to keep constructors similar to

the ones from Classes if explicitly defined. 

I mean if the user is already going to manually write constructors (which 
should be a special case) the few lines of assignments don't matter compared

to the rethinking it needs each time 

with implicits and the restrictions that follow. 


What are your thoughts? 


Kind regards, 


Simon 




 

 



More information about the amber-dev mailing list