Feature request: chainable setters

162 views
Skip to first unread message

benmccann

unread,
Mar 8, 2011, 1:52:25 AM3/8/11
to Project Lombok
Hi,
I think it's be nice if the setters were chainable. E.g. if you have
class person:

public class Person {
private String name;

public Person settName(String name) {
this.name = name;
return this;
}

public String getName() {
return this.name;
}
}

This is very nice because it allows for concise code as a caller
allowing you to do things like:
person.setFirstName("ben").setLastName("mccann");

It's also completely backward compatible.

Thanks,
Ben

rjee

unread,
Mar 8, 2011, 2:20:21 AM3/8/11
to Project Lombok
hey Ben,

chainable setters and getters can be seen as a part of a larger
design, being able to generate a builder class. Perhaps you could
checkout previous discussions we were having about it:

http://groups.google.com/group/project-lombok/browse_thread/thread/2be1317791aadec8/
http://groups.google.com/group/project-lombok/browse_thread/thread/3db3e87521568ee1/
http://groups.google.com/group/project-lombok/browse_thread/thread/906972e9053122da/


regards,

Robbert Jan

Reinier Zwitserloot

unread,
Mar 8, 2011, 8:22:08 AM3/8/11
to project...@googlegroups.com, benmccann
It's also not backwards compatible, for two reasons:

(1) It does not conform to the beanspec. Lombok's current setters do. The bean spec explicitly requires setters to return void, which means that if we update them to return Self, beanspec-based introspection tools will no longer consider them setters.

(2) The return type is part of the signature of methods, at least as far the JVM is concerned. This means changing return type from 'void' to 'Self' is NOT *binary* backwards compatible; if you recompile a library with a hypothetical new lombok that changed the return types and changed just that one jar without also recompiling the user of the library, you'd get NoSuchMethodError.


This means at the very least any Self-returning setter will require something other than @Setter/@Data. Possibly @Setter(fluent=true), more likely what Robbert Jan said: builder classes.

 --Reinier Zwitserloot



--
You received this message because you are subscribed to the Google
Groups group for http://projectlombok.org/

To post to this group, send email to project...@googlegroups.com
To unsubscribe from this group, send email to
project-lombo...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/project-lombok?hl=en

Neildo

unread,
Mar 8, 2011, 3:12:02 PM3/8/11
to Project Lombok
+1 to using something other than setter methods for chaining. I used
to use chained setters but to Reiner's point, there are plenty of
libraries out there that expect setters to follow the bean spec.
Instead I prefer to using "with" methods so you end up with:
new Person().withFirstName("John").withLastName("Doe");

I think "with" is a pretty common naming convention. I don't know
what the annotation would the called. @Wither? That just sounds
wrong. Naming aside, I don't think this should be a builder-specific
feature. Certainly a builder could make use of it though.

--Neil


On Mar 8, 6:22 am, Reinier Zwitserloot <rein...@zwitserloot.com>
wrote:
> It's also not backwards compatible, for two reasons:
>
> (1) It does not conform to the beanspec. Lombok's current setters do. The
> bean spec explicitly requires setters to return void, which means that if we
> update them to return Self, beanspec-based introspection tools will no
> longer consider them setters.
>
> (2) The return type is part of the signature of methods, at least as far the
> JVM is concerned. This means changing return type from 'void' to 'Self' is
> NOT *binary* backwards compatible; if you recompile a library with a
> hypothetical new lombok that changed the return types and changed just that
> one jar without also recompiling the user of the library, you'd get
> NoSuchMethodError.
>
> This means at the very least any Self-returning setter will require
> something other than @Setter/@Data. Possibly @Setter(fluent=true), more
> likely what Robbert Jan said: builder classes.
>
>  --Reinier Zwitserloot
>
> On Tue, Mar 8, 2011 at 7:52 AM, benmccann <benjamin.j.mcc...@gmail.com>wrote:
>
> > Hi,
> > I think it's be nice if the setters were chainable.  E.g. if you have
> > class person:
>
> > public class Person {
> >  private String name;
>
> >  public Person settName(String name) {
> >    this.name = name;
> >    return this;
> >  }
>
> >  public String getName() {
> >    return this.name;
> >  }
> > }
>
> > This is very nice because it allows for concise code as a caller
> > allowing you to do things like:
> > person.setFirstName("ben").setLastName("mccann");
>
> > It's also completely backward compatible.
>
> > Thanks,
> > Ben
>
> > --
> > You received this message because you are subscribed to the Google
> > Groups group forhttp://projectlombok.org/

Maaartin-1

unread,
Mar 8, 2011, 4:06:55 PM3/8/11
to project...@googlegroups.com
On 11-03-08 21:12, Neildo wrote:
> +1 to using something other than setter methods for chaining. I used
> to use chained setters but to Reiner's point, there are plenty of
> libraries out there that expect setters to follow the bean spec.
> Instead I prefer to using "with" methods so you end up with:
> new Person().withFirstName("John").withLastName("Doe");

Isn't "with" used for creating a new instance having all but one field
the same as the original? I'm sure I saw it somewhere (a sort of
immutable builder).

> I think "with" is a pretty common naming convention. I don't know
> what the annotation would the called. @Wither? That just sounds
> wrong. Naming aside, I don't think this should be a builder-specific
> feature. Certainly a builder could make use of it though.

IMHO, a builder and the fluent syntax are quite different things,
although their potential use overlaps a lot.

To Reiner: How should the planned lombok builder actually work?

- Where do you put the annotation?
- - Using @Builder for creating an inner class can't be used for third
party classes.
- - Using @BuilderFor(SomeClass.class) as an annotation on an existing
(possibly empty) class is more general, but requires resolution and a
bit more writing.

I'd prefer @BuilderFor, since it allows to specify defaults in the
Builder's default constructor. An alternative could be to define the
defaults using a special annotation on the fields (simple assigning them
makes no sense, since it'd fix the value in case of final fields, unless
some black magic would be used). Another possibility would be to use a
Prototype.

- Should the builder be field or constructor based?
- - For a field based builder there's no way how to set the final fields
besides a constructor (or using Unsafe, serialization, or something even
more crazy), so it doesn't make much sense.
- - A constructor based builder looks better, but requires to specify
which constructor should be used (this is a problem in case of
@BuilderFor). The constructor based @Builder annotation could be placed
on the constructor directly. Unless the constructor gets generated, too.

Can you provide an example how the builder should work. IIRC, there was
no discussion concerning it yet.

Regards, Maaartin.

Robbert Jan Grootjans

unread,
Mar 8, 2011, 4:28:45 PM3/8/11
to project...@googlegroups.com
On Tue, Mar 8, 2011 at 10:06 PM, Maaartin-1 <graj...@seznam.cz> wrote:
> On 11-03-08 21:12, Neildo wrote:
>> +1 to using something other than setter methods for chaining.  I used
>> to use chained setters but to Reiner's point, there are plenty of
>> libraries out there that expect setters to follow the bean spec.
>> Instead I prefer to using "with" methods so you end up with:
>> new Person().withFirstName("John").withLastName("Doe");
>
> Isn't "with" used for creating a new instance having all but one field
> the same as the original? I'm sure I saw it somewhere (a sort of
> immutable builder).
>
>> I think "with" is a pretty common naming convention.  I don't know
>> what the annotation would the called.  @Wither?  That just sounds
>> wrong.  Naming aside, I don't think this should be a builder-specific
>> feature.  Certainly a builder could make use of it though.
>
> IMHO, a builder and the fluent syntax are quite different things,
> although their potential use overlaps a lot.
>
> To Reiner: How should the planned lombok builder actually work?

Not really Reinier speaking here ;). but I'd like to give a reply. As
in earlier threads, we have stated that there are a lot of things
people would like to see in an eventual @Builder annotation, your
pointing out quit a lot of the requests (annotations on the class
itself, or one pointing to a 3rd party class, fluent getter/setters,
default values, list interpretation). Some of these require a modicum
of resolution, some can 'just work' based on the parsed source, just
like the @Data annotation. What I'm saying is that this is not yet a
closed book, we'd really like to hear your opinion, but we don't think
we're going to get to a proposal before we've released the 0.10.0.

>
> - Where do you put the annotation?
> - - Using @Builder for creating an inner class can't be used for third
> party classes.
> - - Using @BuilderFor(SomeClass.class) as an annotation on an existing
> (possibly empty) class is more general, but requires resolution and a
> bit more writing.

I'd hope we'll get both options in, as both have their equal merits.

>
> I'd prefer @BuilderFor, since it allows to specify defaults in the
> Builder's default constructor. An alternative could be to define the
> defaults using a special annotation on the fields (simple assigning them
> makes no sense, since it'd fix the value in case of final fields, unless
> some black magic would be used). Another possibility would be to use a
> Prototype.
>
> - Should the builder be field or constructor based?
> - - For a field based builder there's no way how to set the final fields
> besides a constructor (or using Unsafe, serialization, or something even
> more crazy), so it doesn't make much sense.
> - - A constructor based builder looks better, but requires to specify
> which constructor should be used (this is a problem in case of
> @BuilderFor). The constructor based @Builder annotation could be placed
> on the constructor directly. Unless the constructor gets generated, too.

I'm not sure about this, but I see two main use cases, either
'upgrading' the existing classes/libraries to get a more fluent/less
verbose builder construct or the case in which your creating a new
system. It would seem to me that the @Builder as you state it would be
used in the second case, in which your probably always going to
generate the constructors, but they'll be private. There are other use
cases, but I think this is the most likely one (correct me if I'm
wrong) and I'd rather let lombok have an annotation that supports
making great builders, than one that supports both having a
constructor *and* a builder... at least, initially. Of course, the
@BuilderFor on 3rd classes is another story entirely.

>
> Can you provide an example how the builder should work. IIRC, there was
> no discussion concerning it yet.
>
> Regards, Maaartin.
>

> --
> You received this message because you are subscribed to the Google

> Groups group for http://projectlombok.org/

Morten Hattesen

unread,
Mar 8, 2011, 4:45:58 PM3/8/11
to project...@googlegroups.com
One key benefit of using the builder pattern (nested builder class) with a private constructor for the main class, is that it allows the main class to be immutable, and thus well suited for concurrency (refer to "Effective Java" and "Concurrency in Practice").

This would obviously require that the main class has NO setter methods generated, and that no other methods are allowed to mutate the built instance.
You should consider an @Immutable annotation (or Immutable option on the @Builder annotation) for this purpose, which may additionally make any fields controlled by the builder "final" (or require them to be final).

/Morten

Fabrizio Giudici

unread,
Mar 8, 2011, 5:06:30 PM3/8/11
to project...@googlegroups.com, Morten Hattesen
On 03/08/2011 10:45 PM, Morten Hattesen wrote:
> One key benefit of using the builder pattern (nested builder class)
> with a private constructor for the main class, is that it allows the
> main class to be immutable, and thus well suited for concurrency
> (refer to "Effective Java" and "Concurrency in Practice").
>
> This would obviously require that the main class has NO setter methods
> generated, and that no other methods are allowed to mutate the built
> instance.
> You should consider an @Immutable annotation (or Immutable option on
> the @Builder annotation) for this purpose, which may additionally make
> any fields controlled by the builder "final" (or require them to be
> final).
>
Immutable stuff is definitely better. But the point is whether Lombok is
a tool to promote best practices or a tool to reduce boilerplate. My
vote is for the latter case, so Lombok should help people do to things
writing as less code as possible, and letting people to decide the
quality of the code they write. Thus I think that Lombok should make
people able to code both things: beans with merely chained setters and
with methods that clone immutable beans. And since there's no strong
standard about the use of "with", Lombok should be flexible. So my 2
cents: @Setter with no params generates a plain void setter; with proper
params, it can create a chainable setter or 'wither'.

BTW, since we're in topic. Did you already discuss how a 'wither' with
immutable stuff could be implemented? To me it doesn't seem easy. I
mean: technically you should clone-everything-but the field you're
changing. But how the clone should be performed? Deep or shallow? If you
think of a simple javabean shallow seems the proper solution, but not
always. Sometimes the builder has e.g. a reference to a context that of
course shouldn't be cloned (in my case, I've a personal pattern which is
a 'finder', that is a builder that instead of creating objects from
scratch, it retrieves them from a data source: hence the need of having
a reference to that context).

--
Fabrizio Giudici - Java Architect, Project Manager
Tidalwave s.a.s. - "We make Java work. Everywhere."
java.net/blog/fabriziogiudici - www.tidalwave.it/people
Fabrizio...@tidalwave.it

Reinier Zwitserloot

unread,
Mar 8, 2011, 6:53:55 PM3/8/11
to project...@googlegroups.com, Fabrizio Giudici, Morten Hattesen
These very interesting questions explain why we haven't tackled this yet :)
 --Reinier Zwitserloot




--

Maaartin-1

unread,
Mar 8, 2011, 8:10:16 PM3/8/11
to project...@googlegroups.com
On 11-03-08 23:06, Fabrizio Giudici wrote:
> Immutable stuff is definitely better. But the point is whether Lombok is
> a tool to promote best practices or a tool to reduce boilerplate. My
> vote is for the latter case, so Lombok should help people do to things
> writing as less code as possible, and letting people to decide the
> quality of the code they write. Thus I think that Lombok should make
> people able to code both things: beans with merely chained setters and
> with methods that clone immutable beans. And since there's no strong
> standard about the use of "with", Lombok should be flexible. So my 2
> cents: @Setter with no params generates a plain void setter; with proper
> params, it can create a chainable setter or 'wither'.

A "wither" is a nice thing. Done manually it involves a copy and paste
of a long constructor call. It's not much work, but looks really funny:

public Something withA(int a) {
return new Something(a, b, c, d, e, f, g);
}
public Something withB(int b) {
return new Something(a, b, c, d, e, f, g);
}

> BTW, since we're in topic. Did you already discuss how a 'wither' with
> immutable stuff could be implemented? To me it doesn't seem easy. I
> mean: technically you should clone-everything-but the field you're
> changing. But how the clone should be performed? Deep or shallow?

IMHO, most of the time you want a shallow copy:
- The fields may be themselves immutable, what the builder can't know.
- The builder has no idea, how to clone my SuperHyperFancyMap, anyway.
- The fields may not cloneable at all, what the builder can't know.
- IMHO, a shallow copy is what most people expect.

A deep copy could be optional. It'd nice if you could provide a Cloner
object to do it for you. But it gets complicated.

However, when you need a deep copy in your "withers", you can do it in
the constructor like in

public Something withC(char[] c) {
return new Something(a, b, c, d, e, f, g);
}
private Something(int a, int b, char[] c, ....) {
this.a = a;
this.b = b;
this.c = c.clone();
....
}

> If you
> think of a simple javabean shallow seems the proper solution, but not
> always. Sometimes the builder has e.g. a reference to a context that of
> course shouldn't be cloned (in my case, I've a personal pattern which is
> a 'finder', that is a builder that instead of creating objects from
> scratch, it retrieves them from a data source: hence the need of having
> a reference to that context).

So shallow cloning your builder (so it receives a reference to the same
context) is the right thing, isn't it?

Regards, Maaartin.

Neildo

unread,
Mar 8, 2011, 8:43:09 PM3/8/11
to Project Lombok
I really like Maaartin's immutable "with" example. It's simple and
truly is boilerplate that you'd never need to debug. Lombok can
already create an all-args constructor that the with methods can use.
It will potentially create a lot of very short-lived objects during
the building process but the GC is good at handling those. I think
the client usage is cleaner than the Josh Bloch builder pattern,
especially when all you want to do is change 1 field on an immutable
object. Josh Bloch's builder pattern example doesn't really address
the latter use case.

-Neil

Fabrizio Giudici

unread,
Mar 9, 2011, 5:36:35 AM3/9/11
to project...@googlegroups.com, Neildo
On 03/09/2011 02:43 AM, Neildo wrote:
> I really like Maaartin's immutable "with" example. It's simple and
> truly is boilerplate that you'd never need to debug. Lombok can
> already create an all-args constructor that the with methods can use.
> It will potentially create a lot of very short-lived objects during
> the building process but the GC is good at handling those. I think
> the client usage is cleaner than the Josh Bloch builder pattern,
> especially when all you want to do is change 1 field on an immutable
> object. Josh Bloch's builder pattern example doesn't really address
> the latter use case.

I strongly disagree that a shallow copy is ok. Indeed, cloning almost
everything but lists and maps just leads to a mutable object that people
believe it's immutable. For instance, it would break some of my code.
But I agree that the clone must be performed in the constructor. So,
it's ok for me to have an annotation for a "wither" that just calls the
constructor. Now, we have to find a way to tell the constructor that we
want a deep copy (which BTW it's a feature by itself, today I have to
override Lombok auto-generated constructors in such cases). So, I'd say
we need a parameter in the constructor annotations to specify we want to
clone parameters. I'd say:

1. of course, no problems for everything is scalar
2. for references, clone() must be called (it's always present, so it's
not a Lombok problem to check for it; eventually, Lombok could emit a
warning if the object doesn't implement Cloneable)
3. references to classes annotated with the standard Java annotation
@Immutable won't be cloned

There's still a remainder point, that as per my previous example there
are still cases of references to mutable objects that one wants to not
clone. We need a way to tell this to Lombok. I'm unsure whether to
specify a list of non-cloned fields in the Lombok constructor
annotations, or whether we introduce another annotation such as
@SharedReference to mark these fields.

Maaartin-1

unread,
Mar 9, 2011, 7:18:44 AM3/9/11
to project...@googlegroups.com
On 11-03-09 11:36, Fabrizio Giudici wrote:
> On 03/09/2011 02:43 AM, Neildo wrote:
>> I really like Maaartin's immutable "with" example. It's simple and
>> truly is boilerplate that you'd never need to debug. Lombok can
>> already create an all-args constructor that the with methods can use.
>> It will potentially create a lot of very short-lived objects during
>> the building process but the GC is good at handling those. I think
>> the client usage is cleaner than the Josh Bloch builder pattern,
>> especially when all you want to do is change 1 field on an immutable
>> object. Josh Bloch's builder pattern example doesn't really address
>> the latter use case.
>
> I strongly disagree that a shallow copy is ok. Indeed, cloning almost
> everything but lists and maps just leads to a mutable object that people
> believe it's immutable.

You're actually right, for public ctors of immutable classes a
"defensive copy" is a must for mutable args. I've been lucky enough to
have no such args.

> For instance, it would break some of my code.
> But I agree that the clone must be performed in the constructor. So,
> it's ok for me to have an annotation for a "wither" that just calls the
> constructor. Now, we have to find a way to tell the constructor that we
> want a deep copy (which BTW it's a feature by itself, today I have to
> override Lombok auto-generated constructors in such cases). So, I'd say
> we need a parameter in the constructor annotations to specify we want to
> clone parameters. I'd say:

Agreed.

> 1. of course, no problems for everything is scalar

No problems for scalars, primitive wrappers, enums, String-s, Class-es,
BigInteger-s, and dozens of other classes.

> 2. for references, clone() must be called (it's always present, so it's
> not a Lombok problem to check for it; eventually, Lombok could emit a
> warning if the object doesn't implement Cloneable)

Calling clone() nearly always is strange. If the clone() method uses
Object.clone() and Cloneable is not implemented, you'll get an
exception. If the object has an usable clone() method and does NOT
implement Cloneable, it's a strange design. Have you ever seen it?

> 3. references to classes annotated with the standard Java annotation
> @Immutable won't be cloned

I've never seen it used. No JDK6 class uses it, not even Guava does
(there are two private classes there annotated with it, that's all).

With calling clone() being the default your constructors would nearly
always throw. You mentioned no way how to make it work e.g. for
com.google.common.collect.ImmutableMap, which is immutable, but not
annotated as such. I'm afraid, not cloning must be the default for
everything except for cloneables.

Neither I see how you want to handle e.g. a StringBuilder, which is
trivial to clone manually, but doesn't implement Cloneable.

> There's still a remainder point, that as per my previous example there
> are still cases of references to mutable objects that one wants to not
> clone. We need a way to tell this to Lombok. I'm unsure whether to
> specify a list of non-cloned fields in the Lombok constructor
> annotations, or whether we introduce another annotation such as
> @SharedReference to mark these fields.

Here, the situation is probably the same as with @EqualsAndHashCode.
This is a very simple decision in comparison to all the other problems.

Reinier Zwitserloot

unread,
Mar 9, 2011, 8:41:44 AM3/9/11
to project...@googlegroups.com, Fabrizio Giudici, Neildo
This withX could work. We don't need to solve the constructor problem at all, we just need to document it: If cloning is needed, then write your own constructor. Cloning is fundamentally not a boilerplate exercise; at best we can clone arrays for you, and maybe java.util collections (though, what if your field is "Map" but for practical reasons you want a LinkedHashMap and not a plain HashMap there? So, not truly boilerplate).

@Immutable is very tricky. What does it even mean? Is File immutable? What about an instance of a Logger?


From a practical standpoint, cloning everything is usually not the right solution, and even if it was, calling .clone() to do so usually isn't the right solution either.

Introducing a bevy of annotations that go on fields to tell lombok what to do is just trading one kind of boilerplate for another. Roel and I both strongly feel this is a bad idea as its tantamount to introducing a DSL, and that's not what lombok is for.

 --Reinier Zwitserloot



--

Reinier Zwitserloot

unread,
Mar 9, 2011, 8:43:53 AM3/9/11
to project...@googlegroups.com, Maaartin-1
Also, its often the type of the referent and not of the variable that has immutability info.

For example, List isn't Immutable, but guava's ImmutableList is.


Also, any non-final class cannot be treated as @Immutable if we're looking at field types.

... but looking at referent types means introducing lots of reflection, which just sounds like a bad idea (but that's gut instinct, possibly that's the answer).

 --Reinier Zwitserloot



Fabrizio Giudici

unread,
Mar 9, 2011, 9:21:07 AM3/9/11
to project...@googlegroups.com, Maaartin-1
On 03/09/2011 01:18 PM, Maaartin-1 wrote:
> @Immutable

> I've never seen it used. No JDK6 class uses it, not even Guava does
> (there are two private classes there annotated with it, that's all).
Well, that's a pity for JDK 6 and Guava :-) But I use them, as for the
other sisters from Concurrency In Practice. They are part of JSR 305 and
have a standard javax package. They are also well defined semantically,
in fact FindBugs can automatically check whether they are used in a
consistent way. That's why I suggest that Lombok can take advantage of
them - if one use them in a inconsistent way, it's a problem of that
guy, as if it implemented equals() in a broken way.

For what concerns annotations in fields, as per Reiner's remark, I
understand it's not an elegant way. In any case, it seems that the point
we're making that moving the cloning issues back to the constructor we
can simply define the behaviour of a whiter, thus it could be
implemented independently of the clone stuff.

Maaartin-1

unread,
Mar 9, 2011, 6:47:24 PM3/9/11
to project...@googlegroups.com
On 11-03-09 14:41, Reinier Zwitserloot wrote:
> This withX could work. We don't need to solve the constructor problem at
> all, we just need to document it: If cloning is needed, then write your
> own constructor. Cloning is fundamentally not a boilerplate exercise; at
> best we can clone arrays for you, and maybe java.util collections
> (though, what if your field is "Map" but for practical reasons you want
> a LinkedHashMap and not a plain HashMap there? So, not truly boilerplate).

Using LinkedHashMap.clone() you get an Object (shame for JDK), which in
fact is a LinkedHashMap.

> @Immutable is very tricky. What does it even mean? Is File immutable?

If File is not immutable, then String isn't either, as there's nothing
you can do with File but not with String (which is a strange API design).

File must be immutable, as it's just an immutable handle identifying
some other object. You could even use primitives for identifying files
(e.g. by numbering files in a directory) and primitives are immutable,
aren't they?

> What about an instance of a Logger?

From practical POV it's immutable, since no sane program cares about its
state. But I agree that @Immutable is very tricky.

> From a practical standpoint, cloning everything is usually not the right
> solution, and even if it was, calling .clone() to do so usually isn't
> the right solution either.

Agreed. Moreover, clone() was never meant as deepClone().

> Introducing a bevy of annotations that go on fields to tell lombok what
> to do is just trading one kind of boilerplate for another. Roel and I
> both strongly feel this is a bad idea as its tantamount to introducing a
> DSL, and that's not what lombok is for.

I can't imagine how annotations could exactly describe what the lombok's
user needs. There are just too many possibilities. For example, you may
want to do no cloning in the package-private ctor and push it to the
public factory method.

(continued)

On 11-03-09 14:43, Reinier Zwitserloot wrote:
> Also, its often the type of the referent and not of the variable that
> has immutability info.
>
> For example, List isn't Immutable, but guava's ImmutableList is.

To make it worse: Depending on what it contains.

> Also, any non-final class cannot be treated as @Immutable if we're
> looking at field types.

You could treat all classes annotated with @Immutable as immutable, just
take it as the implementor's responsibility.

> ... but looking at referent types means introducing lots of reflection,
> which just sounds like a bad idea (but that's gut instinct, possibly
> that's the answer).

Reflection means you want to do it all at runtime, right? This has some
implications:
- A "universal cloning code" is fairly long )not complicated, but needs
to handle a lot of cases, even when restricted to the JDK).
- You surely don't want to blow up the code by copying the beast to each
constructor.
- This means that you either have to introduce a runtime dependency on
lombok whenever this feature gets used (which would be IMHO acceptable)
or emit the beast class in the way APT processors do (which would be
IMHO nicer).

However, you could not handle user's classes, thus possibly missing the
more important part. So instead of incorporating a complicated cloner
into lombok you could let the user do most of the work like follows:

@RequiredArgsConstructor(cloner=com.example.MyCloner.class)
class MyClass {
private final Object a, b;
private final int n;
}

->

class MyClass {
private final Object a, b;
private final int n;
public MyClass(Object a, Object b, int n) {
com.example.MyCloner $cloner = new com.example.MyCloner();
this.a = $cloner.clone(a);
this.a = $cloner.clone(a);
this.n = n;
}
}

There's some magic involved, but not much:
- Requiring a no-args ctor is quite common.
- Requiring a method "T clone(T)" is about the same as requiring
"close()" for @Cleanup.

This looks like shifting all the work to runtime, but not really. It
could look like

public class MyCloner {
MyImmutable clone(MyImmutable x) {
return x;
}
....
<T> T clone(T x) { // fallback method
return ReflectionMagic.clone(x);
}
}

I think there's neither much magic nor much work involved and it should
satisfy all Fabrizio's needs (and not only his).

Maaartin-1

unread,
Mar 9, 2011, 6:53:40 PM3/9/11
to project...@googlegroups.com
On 11-03-09 15:21, Fabrizio Giudici wrote:
> On 03/09/2011 01:18 PM, Maaartin-1 wrote:
>> @Immutable
>> I've never seen it used. No JDK6 class uses it, not even Guava does
>> (there are two private classes there annotated with it, that's all).

> Well, that's a pity for JDK 6 and Guava :-)

I'd suppose, for JDK6 it'll take some years (or centuries) till they
start to use it. For Guava, an ImmutableList<String> is @Immutable, but
ImmutableList<Object> is not. I had a look at a couple of other classes
and there's nothing where it could be put on.

Maybe the definition of @Immutable is too restrictive. Maybe it should
be allowed on non-final classes and delegate the responsibility to the
subclass implementer. Actually, when you want, you may mutate everything
using reflection, Unsafe, or JNI, so relaxing @Immutable this way makes
sense. This way CharMatcher could be @Immutable, too. Not so much gained.

Restricting the subclasses to be @Immutable by contract may be a
problem, too. What if you really need a mutable subclass? What if you
need some caching? Are you allowed to do the caching in a thread-safe way?

> But I use them, as for the
> other sisters from Concurrency In Practice. They are part of JSR 305 and
> have a standard javax package. They are also well defined semantically,
> in fact FindBugs can automatically check whether they are used in a
> consistent way. That's why I suggest that Lombok can take advantage of
> them - if one use them in a inconsistent way, it's a problem of that
> guy, as if it implemented equals() in a broken way.

The missing @Immutable on someone's classes is not a their problem but
ours. For JDK there's a simple solution: hardwiring the information into
lombok. For third party classes it's harder.

> For what concerns annotations in fields, as per Reiner's remark, I
> understand it's not an elegant way. In any case, it seems that the point
> we're making that moving the cloning issues back to the constructor we
> can simply define the behaviour of a whiter, thus it could be
> implemented independently of the clone stuff.

I'm afraid, it's too complicated to be handled automatically.

Reinier Zwitserloot

unread,
Mar 9, 2011, 11:26:15 PM3/9/11
to project...@googlegroups.com
Maybe. I'm not convinced allowing you to shell out the work to some specified class is much of an improvement over the much simpler:

 We'll just shallow copy everything. Don't like it? Write your own constructor.


This applies _anyway_, as the constructor serves for more than just cloning, unless of course you make it private.

Fabrizio Giudici

unread,
Mar 10, 2011, 4:18:07 AM3/10/11
to project...@googlegroups.com, Maaartin-1
On 03/10/2011 12:53 AM, Maaartin-1 wrote:
>
> Maybe the definition of @Immutable is too restrictive.
Note that in my original post I didn't say that having @Immutable is a
*needed* condition for guessing that Lombok should not clone something.
I said it is *enough* and takes care of some scenarios. Then, there are
cases in which you can't have @Immutable (the cache example you made is
a very frequent one, or the context reference in a Finder, where the
reference clearly points to a stateful and mutable object, but it's the
service you call to get some data, this it is intended to be share).
That's my whole point, that in order to automatically support clonining
in Lombok we need some further information. If we decide that it's too
cumbersome to support it, then we conclude that we can't do this in
Lombok. I think it would be a valuable point in any case, so one could
decide to write his own extension for the topic.

But I said previously that this is very independent from the wither
stuff, if we just decide that the wither calls a constructor. The wither
indeed has only to call the constructor and then it's up to the
programmer to eventually provide it in the good way, as Reiner's said.

Graham Allan

unread,
Mar 10, 2011, 6:21:49 PM3/10/11
to project...@googlegroups.com, Maaartin-1
> If File is not immutable, then String isn't either, as there's nothing
> you can do with File but not with String (which is a strange API design).
>
> File must be immutable, as it's just an immutable handle identifying
> some other object. You could even use primitives for identifying files
> (e.g. by numbering files in a directory) and primitives are immutable,
> aren't they?

I know this is getting a bit off-topic, but immutability is a kinda pet topic
of mine.

The difference between File and String is that there are methods of File, where
the same instance can return different results at different times. Granted the
method results depend on the state of some other thing (the underlying
filesystem) and iirc File instances themselves don't have any state to mutate,
but I think a 'turtles all the way down' kind of immutability is the most
accepted definition. Files and loggers fall into a side-effect-free distinction
that's subtly different enough to not be considered immutable.

For Files, it's only the reference that's immutable i.e. a File instance will
always point to the same place on the filesystem, but the state of the instance
itself is mutable, since isEmpty() can return true for one call, then false
for a subsequent call.

I'd like to hear your thoughts on that, unless it's getting too off-topic for
this list, in which case I apologise :)

Regards,
Graham

Reinier Zwitserloot

unread,
Mar 10, 2011, 11:17:32 PM3/10/11
to project...@googlegroups.com, Graham Allan
Well, you nailed it. Immutable is a vague term in practice.

There's also the difference between Immutable, which is primarily a term associated with a type, and Side-Effect-Free (SEF) which is principally a property of a method, though I guess one could call an entire type SEF if all of its methods are SEF.

A rigidly checked and relied-upon annotation to convey these properties is not feasible for java, for a few reasons:

(A) Any non-final class cannot be relied upon to be Immutable. We'd need a compiler that can somehow check that any subclasses don't violate the constraint, but such a thing wouldn't be backwards compatible in general, so we'll in practice either have to inspect the type of an object itself with reflection, or take it as a light hint.

(B) All types are non-SEF out of the box, because Object.wait/notify/notifyAll are all non-SEF. Thus SEFness can only be ascribed to methods which is kind of a shame.


A 'true' immutable, i.e. a class where there's absolutely no doubt of any kind, is both SEF (except w/n/nA) and Immutable for all workable definitions of either of those terms. String would appear to be such a class.

However, even string is in practice not truly SEF/Immutable either; internally it caches the hash calculation, for starters.


We can't simplify away the correlation between SEF and Immutable, either: If we define 'immutable' simply as: All fields are (A) final and (B) of a type that is itself immutable, then we can create a trivially immutable class that acts 100% equal to a mutable one:


public class FakeImmutable {
    private static final IdentityHashMap<FakeImmutable, String> mutableName = new ....;
    public void setName(String name) { mutableName.put(this, name); }
    public String getName() { return mutableName.get(this); }
}


Only by pulling SEF into the mix can we avoid this kind of skirting of immutability.


It's such a complicated mess that I've stopped considering either property as ready to include in java.

I've also recently abandoned the notion that the future *of programming* is multi-core. (The future of chips most likely will be, but I no longer believe code needs to take advantage of it at a fine-grained level). Thus the immediate pressure of highly integrated immutable/SEF annotations so the JVM can silently cache and multicore operations is IMO not an immediate worry. This is a topic for another thread, I guess, but the gist of it is simply that we programmers have elevated the abhorrence of optimizations on a scale of 2x-20x to ridiculous levels ("Premature optimization is the root of all evil"), and we've learned that changing code structure for such trifling speedups is just plain bad. And yet we're all ready to make some of the biggest changes to our development model yet for speedups of at best that scale for a very long time to come, whilst at the same time the vast majority of truly performance sensitive stuff I've run into was very easy to large-grain parallelize, which doesn't require any language features. (i.e. handling multiple HTTP requests in multiple threads, that's large-grain parallelizing).


 --Reinier Zwitserloot



Daniel López

unread,
Mar 11, 2011, 1:54:58 PM3/11/11
to project...@googlegroups.com
I agree that the inmutable/builder factory is quite a tricky subject to tackle in a satisfactory manner, so I'd rather see a simple "wither" annotation (a getter + return this) that is simple to understand and easier to produce and let the programmer deal with the inmutability problem. Better to solve a tiny problem right than trying to tackle the whole thing and create lots of problems due to misunderstandings, people not reading carefully the docs. etc.

Just my 2ec extrictly as a user,
D.

2011/3/11 Reinier Zwitserloot <rei...@zwitserloot.com>

Reinier Zwitserloot

unread,
Mar 11, 2011, 3:20:28 PM3/11/11
to project...@googlegroups.com, Daniel López
So should we build the wither now (we can get that out the door relatively quickly, i.e. 0.10.1) or should we wait and design the full builder annotation first in case it obsoletes the wither? Or will a wither always be useful.

Also, @Wither is not a name I'm comfortable with. How about a parameter on @Setter? A 'normal' (beanspec compatible) setter can't coexist with a wither.

 --Reinier Zwitserloot

Maaartin-1

unread,
Mar 12, 2011, 1:42:11 AM3/12/11
to project...@googlegroups.com
On 11-03-11 21:20, Reinier Zwitserloot wrote:
> So should we build the wither now (we can get that out the door
> relatively quickly, i.e. 0.10.1) or should we wait and design the full
> builder annotation first in case it obsoletes the wither? Or will a
> wither always be useful.

I'm assuming you mean a wither in the sense of chainable setter, not in
sense of the thing creating a new immutable instance with one field
replaced.

IMHO, it'll always be useful, since you can use it on existing
instances, too. OTOH, it'd be most useful on beans, where it's
incompatible with the beanspec. Couldn't you change the beanspec? :D

> Also, @Wither is not a name I'm comfortable with. How about a parameter
> on @Setter? A 'normal' (beanspec compatible) setter can't coexist with a
> wither.

It'd be nice if the chainability could be specified on the whole class
even in case you don't want setters on each field like

@Setter(chainable=true, value=AccessLevel.NONE)
class C {
@Setter int a;
/* no setter */ int b;
}

This is a bit strange, since it means that the Setter.chainable on the
whole class should be "inherited" by the field, but Setter.value should
not. OTOH, I can't imagine why anybody would like to combine chainable
and non-chainable setters in a single class.

Daniel López

unread,
Mar 12, 2011, 8:31:18 AM3/12/11
to project...@googlegroups.com
IMHO, I think a wither would always be useful, even if later on we manage to combine it with a builder feature, the same way @Data "includes" @Getter/@Setter, @ToString etc.

And given that, as you mentioned before, turning setters into "withers" would make classes incompatible and they would not conform to the Bean spec., I'd rather have the new annotation create new methods, for example using 'with' instead of 'get' and simply re-use the Setter creation + return this.
This way users of the annotation can have their beans still conform to the spec. and they can use it in old code without breaking anything unsuspectedly. The main reason to combine setters with "withers", I would think, comes your classes having to be handled by libraries expecting them to behave like beans while you want to be able to play with them in a more comfortable way.

Another plus I like of that option is that it is the most simple and predictable, meaning no compatibilty surprises, no methods that look like setters but are different and reflection-incompatible, simple behaviour...

I'd propose something like @ChainedSetter, or @Setter(style="bean|chained|both") if we want to re-use the @Setter annotation

My 2ec,
D.

2011/3/12 Maaartin-1 <graj...@seznam.cz>

--

Reinier Zwitserloot

unread,
Mar 12, 2011, 12:37:11 PM3/12/11
to project...@googlegroups.com

As I mentioned before, withX () as method name is not an option as it implies creating a new object that is a clone except for this one field.

It can be setX() or just x().

On Mar 12, 2011 6:29 PM, "Morten Hattesen" <morten....@gmail.com> wrote:
> I feel that it is a mistake to ask for Lombok to break the bean-spec (i.e.
> that setters should return void).
> While I can see the benefit in having "withers", I feel that these should
> NOT be called setXxx(), but rather withXxx(), using this signature:
>
> public ThisType withXxx(FieldType xxx) {
> this.xxx = xxx;
> return this;
> }
>
> Breaking the bean-spec will result in the generated classes being
> incompatible with all current context/dependency injection frameworks, such
> as Spring, Guice, Seam, Java CDI et.al.
>
> Just my 0.02 worth,
>
> Morten

Morten Hattesen

unread,
Mar 12, 2011, 12:29:38 PM3/12/11
to project...@googlegroups.com
I feel that it is a mistake to ask for Lombok to break the bean-spec (i.e. that setters should return void).
While I can see the benefit in having "withers", I feel that these should NOT be called setXxx(), but rather withXxx(),  using this signature:

public ThisType withXxx(FieldType xxx) {
    this.xxx = xxx;
    return this;
}

Breaking the bean-spec will result in the generated classes being incompatible with all current context/dependency injection frameworks, such as Spring, Guice, Seam, Java CDI et.al.

Just my 0.02 worth,

Morten

On 12 March 2011 14:31, Daniel López <d.lo...@gmail.com> wrote:

Morten Hattesen

unread,
Mar 13, 2011, 4:51:53 AM3/13/11
to project...@googlegroups.com
As I mentioned before, withX () as method name is not an option as it implies creating a new object that is a clone except for this one field.

OK, in that case I'll strongly recommend NOT breaking the bean-spec by making setX() return anything but void. You wouldn't want Lombok to be known as the product that makes your code non-compliant with ubiquitous standards and coventions, right?

/Morten 

Maaartin-1

unread,
Mar 13, 2011, 6:22:19 AM3/13/11
to project...@googlegroups.com
On 11-03-13 09:51, Morten Hattesen wrote:
> As I mentioned before, withX () as method name is not an option as
> it implies creating a new object that is a clone except for this one
> field.
>
>
> OK, in that case I'll strongly recommend NOT breaking the bean-spec by
> making setX() return anything but void. You wouldn't want Lombok to be
> known as the product that makes your code non-compliant with ubiquitous
> standards and coventions, right?

Can you come up with a better name? Maybe simply removing the prefix
altogether and using

class C {
private int x;
C x(int x) {
this.x = x;
}
}

is the way to go?


Btw., Guice doesn't care about the setters returning anything. In fact
it doesn't care about setters at all, just give it a method like

@Inject
boolean doSomething(@Named("limit") int limit, MyFancyThing thing);

and it'll call it.

Regards, Maaartin.

Reinier Zwitserloot

unread,
Mar 13, 2011, 10:45:04 AM3/13/11
to project...@googlegroups.com

Yes, just x(val) is the frontrunner right now.

Fabrizio Giudici

unread,
Mar 13, 2011, 11:12:49 AM3/13/11
to project...@googlegroups.com, Reinier Zwitserloot
My points:

* thanks to Allan for the discussion about File (im)mutability. I think
that the discussion about immutability is interesting, even though not
pertinent here over a certain threshold... I think I'm going to post
something at the JavaPosse :-)
* "withers" are anyway useful independently of builders, so let's go;

I'm rather perplexed with the name thing:

* I agree that 'wither' is not good for anything else than a discussion
* I agree that current plain setters must be kept as void
* I disagree about the rest of the points :o)

In particular, I don't understand why it's so important to prevent
people from creating a setter which is not void (apart from backward
compatibility, of course). Yes, there's a precise *documented
convention* about setters to return void. But there's also a precise
*language specification* that mandates some rules about checked
exceptions and these are happily violated by @SneakyThrows. Smells like
a double standard, right? :-)

On the other side, there's no *documented convention* about withXXX() to
return a clone. It's just a convention used by some people. I must add
that javax.persistence.Query is a standard Java API and its setters
return this. So I don't see the point for Lombok being more picky
(pickier?) than the Java runtime itself. Let the programmers decide. My
proposal:

@Setter(chainable=true, prefix="with")

chainable defaults to false and prefix to "set", so we're backward
compatible. This makes it possible for people to use chainable setters
(chainable=true, prefix="set"), withers (chainable=true, prefix="with"),
or even simple methods such as x() (chainable=true, prefix="").

Of course, style=something in place of chainable=true is fine for me all
the way.

Reinier Zwitserloot

unread,
Mar 13, 2011, 11:29:48 AM3/13/11
to Fabrizio Giudici, project...@googlegroups.com
Interesting idea. Next evening Roel and Robbert Jan and I spend lombok hacking this proposal (including the prefix=, chainable= version) will be discussed. Its major flaw (and it's not much of a flaw), is that you can't use it to generate 2 different setters, i.e. if you want setX() that's bean compatible, and x() that returns Self.

 --Reinier Zwitserloot

Maaartin-1

unread,
Mar 13, 2011, 11:50:44 AM3/13/11
to project...@googlegroups.com
On 11-03-13 15:45, Reinier Zwitserloot wrote:
> Yes, just x(val) is the frontrunner right now.

It looks nice, but aren't there frameworks requiring the "set" prefix
while ignoring the return type? I don't recall any, but I think they
exist as there's no sane reason to care about the return type. Unlike
generating non-void setters, whenever you see something like "setX(x)"
you may IMHO assume it's a setter without breaking anything.

(continued)

On 11-03-13 16:12, Fabrizio Giudici wrote:
> My points:
>
> * thanks to Allan for the discussion about File (im)mutability. I think
> that the discussion about immutability is interesting, even though not
> pertinent here over a certain threshold... I think I'm going to post
> something at the JavaPosse :-)
> * "withers" are anyway useful independently of builders, so let's go;
>
> I'm rather perplexed with the name thing:
>
> * I agree that 'wither' is not good for anything else than a discussion
> * I agree that current plain setters must be kept as void
> * I disagree about the rest of the points :o)
>
> In particular, I don't understand why it's so important to prevent
> people from creating a setter which is not void (apart from backward
> compatibility, of course). Yes, there's a precise *documented
> convention* about setters to return void. But there's also a precise
> *language specification* that mandates some rules about checked
> exceptions and these are happily violated by @SneakyThrows. Smells like
> a double standard, right? :-)

Not really, @SneakyThrows is not an implementation of something in a way
violating some rules, it's a feature meant to break the rules. Nobody
using @SneakyThrows can complain about it working wrongly or in a
non-standard fashion, since by it's very use you commit to breaking the
rules.

> On the other side, there's no *documented convention* about withXXX() to
> return a clone. It's just a convention used by some people. I must add
> that javax.persistence.Query is a standard Java API and its setters
> return this. So I don't see the point for Lombok being more picky
> (pickier?) than the Java runtime itself. Let the programmers decide. My
> proposal:

Good point!

> @Setter(chainable=true, prefix="with")
>
> chainable defaults to false and prefix to "set", so we're backward
> compatible.

Not exactly, because of the strange set/is convention. I'd reserve the
prefix "set" for using "set" systematically and use "-" (or whatever)
for the set/is obsession.

> This makes it possible for people to use chainable setters
> (chainable=true, prefix="set"), withers (chainable=true, prefix="with"),
> or even simple methods such as x() (chainable=true, prefix="").

The downside is that people may use it in a non-consequent way ending
with some strange mixture of differently named setters in different
classes. OTOH, this could happen without lombok, too (albeit it's less
probable, since forgetting the args to @Setter is easier than writing a
setter differently). Some global option would be nice...

> Of course, style=something in place of chainable=true is fine for me all
> the way.

Regards, Maaartin.

Fabrizio Giudici

unread,
Mar 13, 2011, 11:54:37 AM3/13/11
to rei...@zwitserloot.com, Reinier Zwitserloot, project...@googlegroups.com
On 03/13/2011 04:29 PM, Reinier Zwitserloot wrote:
> Interesting idea. Next evening Roel and Robbert Jan and I spend lombok
> hacking this proposal (including the prefix=, chainable= version) will
> be discussed. Its major flaw (and it's not much of a flaw), is that
> you can't use it to generate 2 different setters, i.e. if you want
> setX() that's bean compatible, and x() that returns Self.

I think it can be resolved by changing the syntax of the annotation
attributes. I mean, it's a sort of challenge to keep the syntax short
(*), but the conceptual point is that Lombok should let the programmer
decide how to (ab)use setters and withers, leaving at least design
principles out of the scenario.


(*) Too bad they didn't allowed multiple same-class annotations on the
same syntatical element. We could just have:

@Setter @Setter(chainable=true, prefix="with")
private int x;

Maaartin-1

unread,
Mar 13, 2011, 12:12:31 PM3/13/11
to project...@googlegroups.com
On 11-03-13 16:29, Reinier Zwitserloot wrote:
> Interesting idea. Next evening Roel and Robbert Jan and I spend lombok
> hacking this proposal (including the prefix=, chainable= version) will
> be discussed. Its major flaw (and it's not much of a flaw), is that you
> can't use it to generate 2 different setters, i.e. if you want setX()
> that's bean compatible, and x() that returns Self.

There's an easy solution. Let

@Setter(chainable=true, prefix="", standard=true)

generate the fancy setter in addition to the conforming one. But three
options are not nice and generating two non-chainable setters makes no
sense, so maybe something like

enum SetterStyle {DEFAULT, CHAINABLE, BOTH}

together with

public @interface Setter {
SetterStyle setterStyle() default SetterStyle.DEFAULT;
String prefix default "-";
...
}

could be the way to go.

@Setter =>
void setX(int x);

@Setter(setterStyle=CHAINABLE) =>
Something x(int x);

@Setter(setterStyle=BOTH) =>
void setX(int x);
Something x(int x);

@Setter(setterStyle=CHAINABLE, prefix="with") =>
Something withX(int x);

@Setter(setterStyle=BOTH, prefix="with") =>
void setX(int x);
Something withX(int x);

@Setter(setterStyle=BOTH, prefix="set") =>
error

Unfortunately, it's bit verbose; as Fabrizio said,


@Setter @Setter(chainable=true, prefix="with")

would be much nicer.


I would in no case use the "with" prefix as default, as there may be a
"real wither" one day returning a new instance of an immutable type.

Reinier Zwitserloot

unread,
Mar 13, 2011, 12:31:38 PM3/13/11
to project...@googlegroups.com, Maaartin-1
If we have a SetterStyle, we can just multi-args it like so:

public enum SetterStyle { RETURN_VOID, RETURN_SELF }

... but this won't help at all, because you need 1 'prefix' for each style. You could nest annotations:

@Setters({
    @Setter(style=RETURN_VOID, prefix="set"),
    @Setter(style=RETURN_SELF, prefix="")})

But I'll save you the trouble and torpedo this unwieldy construct right now. This will not happen.

Let's keep looking. How about this marvel of brevity:

@Setter(standard=true, returnSelf="")

The trick here is that 'standard' has a dynamic default; even though in the annotation definition it has a default of 'true', that's not its real default. Lombok can detect if you explicitly specified it or not, and will default it to 'true' if returnSelf is not present, but default it to 'false' if returnSelf is present. Thus, @Setter with nothing gets you the usual setters, but @Setter(returnSelf="") will not. If you want both, you write the above snippet.

This approach has 2 problems: (A) returnSelf="" isn't 'readable'; at a glance, without knowing the specifics, you have no idea what the heck that means. (B) the screwy default behaviour could confuse people as many will probably think that kind of defaulting behaviour is impossible (which it usually is, but lombok does the impossible, that's sort of the point :P).

I can solve A by introducing some redundancy in the syntax:

@Setter(standard=true, returnSelf=true, returnSelfPrefix="with")

The returnSelfPrefix will default to "". Lombok will generate an error if you explicitly specify returnSelfPrefix but also forget to add 'returnSelf=true'. I'm guessing 99% of all @Setter annotations will take one of these two forms, and both are very short:

@Setter

@Setter(returnSelf=true)

With the ability to specify 'standard=true' and 'returnSelfPrefix' for the very rare cases where people feel they need it. This also means lombok strongly steers users towards a specific prefix, hopefully engendering some sort of de-facto standard (Roel, Robbert Jan and I all agree that justFieldName(val) is the best one, so that's what it'll be).


How'd that be? The weird defaulting behaviour of 'standard' too confusing? Is 'standard' the right name, or should it be something like 'beanspec'? To solve this final issue:

@Setter(alsoGenerateStandardSetters=true, returnSelf=true)

could work, and then alsoGenerateStandardSetters could default to false, and it would then mean: If 'false', only generate beanspec setters if returnSelf is false. If 'true', always generate them. This last version is by far the most readable, and typing-wise not much of a stretch as presumably folks will let their IDE auto-complete 'alsoGenerateStandardSetters' if they need it. It's going to be a rather rare option anyway.


 --Reinier Zwitserloot




--

Philipp Eichhorn

unread,
Mar 13, 2011, 5:56:35 PM3/13/11
to Project Lombok
A long while ago I added chainable setters to my lombok folk. Along
the way I moved from the configuration of the @Setter annotation to a
separate annotation, something like @ChainableSetter or @FluentSetter.
And would prefer it still, as long as the annotation communicates its
purpose well enough.
Just another idea for the sake of completeness.

--Philipp Eichhorn

On Mar 13, 5:31 pm, Reinier Zwitserloot <rein...@zwitserloot.com>
wrote:
> > Groups group forhttp://projectlombok.org/

Reinier Zwitserloot

unread,
Mar 14, 2011, 10:24:36 PM3/14/11
to project...@googlegroups.com
We (Roel, Robbert Jan and I) have been discussing chainable setters and we've decided on a rather drastic change to @Data, @Setter, and @Getter instead of the painstakingly backwards compatible plans discussed here so far. I fired up a new thread for this, which you can find here:


We've removed all support for picking your own prefix, mostly because we don't see the value at this point in time of offering that feature. I doubt lombok will be used to replace vast tracts of code that already exists and follows to the letter what lombok does _except_ for the prefix. While counter-examples do exist, we still feel the 'with' prefix creates the impression that you're getting a new instance with all but the one field the same, and return-this based setters that are still named 'setX' and 'getX', while clearly the best use case for allowing you to set a prefix, might cause someone to think they are bean spec compatible when they aren't. Also, we can always choose to add 'prefix' support later if we get enough requests for it, whereas if we add it now and few actually want it we can never remove it without causing another backwards incompatible upset. We've learned NOT to add options unless we're 100% certain they are absolutely necessary, and this doesn't seem _absolutely_ necessary, so best to let experience show us.

Maaartin-1

unread,
Mar 26, 2011, 11:27:10 AM3/26/11
to project...@googlegroups.com
On 11-03-08 22:28, Robbert Jan Grootjans wrote:
>> To Reiner: How should the planned lombok builder actually work?
>
> Not really Reinier speaking here ;). but I'd like to give a reply. As

Thank you, I see, I misspelled his name anyway, sorry for it.

> in earlier threads, we have stated that there are a lot of things
> people would like to see in an eventual @Builder annotation, your
> pointing out quit a lot of the requests (annotations on the class
> itself, or one pointing to a 3rd party class, fluent getter/setters,
> default values, list interpretation). Some of these require a modicum
> of resolution, some can 'just work' based on the parsed source, just
> like the @Data annotation. What I'm saying is that this is not yet a
> closed book, we'd really like to hear your opinion, but we don't think
> we're going to get to a proposal before we've released the 0.10.0.
>
>>
>> - Where do you put the annotation?
>> - - Using @Builder for creating an inner class can't be used for third
>> party classes.
>> - - Using @BuilderFor(SomeClass.class) as an annotation on an existing
>> (possibly empty) class is more general, but requires resolution and a
>> bit more writing.
>
> I'd hope we'll get both options in, as both have their equal merits.

Thank you for this info. I hope too.

>> - Should the builder be field or constructor based?
>> - - For a field based builder there's no way how to set the final fields
>> besides a constructor (or using Unsafe, serialization, or something even
>> more crazy), so it doesn't make much sense.
>> - - A constructor based builder looks better, but requires to specify
>> which constructor should be used (this is a problem in case of
>> @BuilderFor). The constructor based @Builder annotation could be placed
>> on the constructor directly. Unless the constructor gets generated, too.
>
> I'm not sure about this, but I see two main use cases, either
> 'upgrading' the existing classes/libraries to get a more fluent/less
> verbose builder construct or the case in which your creating a new
> system. It would seem to me that the @Builder as you state it would be
> used in the second case, in which your probably always going to
> generate the constructors, but they'll be private. There are other use
> cases, but I think this is the most likely one (correct me if I'm
> wrong) and I'd rather let lombok have an annotation that supports
> making great builders, than one that supports both having a
> constructor *and* a builder... at least, initially. Of course, the
> @BuilderFor on 3rd classes is another story entirely.

IMHO, one of the most important uses of a builder is to replace long
argument list of constructors. Actually, it could be used to replace all
long argument lists. For other methods it's not as important as for
ctors, but thinking about a *builder for an argument list* instead of
for an object may itself be fruitful.

I had a look at some ctors which could make use of a builder:

Thread(ThreadGroup group, Runnable target, String name, long stackSize)
There exist 8 out of possible 16 ctors, which is surely sufficient, just
not very nice. With all argument types being different there's no
confusion with the overloading. In any case a builder would be much nicer.

For e.g. Font and Dialog there are multiple ctors, but it all is too
confused to be worth digging in deeply.

An interesting and complex example of a (sort of) builder is the
MapMaker, which contains about 10 settable fields (or such planned to
settable in the future like e.g. keyEquivalence). There are some tests
in each method which may make the automatic generation of such a builder
complicated. For example, it tests that the evictionListener is not null
and that the maximumSize is non-negative. IMHO, the former could do
Lombok using an annotation, but there's no standard annotation for the
latter. While non-negativity is quite a common requirement, if you
started to create a corresponding annotation, the question is where to
stop. Maybe something like

@NotNull MapEvictionListener evictionListener;
@CheckedBy(MyNonNegativityChecker.class) int maximumSize;

could work. Somehow. Otherwise the checks had to be postponed to the
ctor which wouldn't be fail-fast.

There are also checks for not specifying a value twice, which may
capture some errors but limit the usability a bit. This should be
probably optional in a general builder.

There are also some checks for combined conditions like not specifying
both expireAfterAccess and expireAfterWrite. I don't think this
could/should be automated at all. I wonder if there's a way for
combining such checks with lombok-generated code.

Regards, Maaartin.

Reinier Zwitserloot

unread,
Mar 26, 2011, 1:38:27 PM3/26/11
to project...@googlegroups.com, Maaartin-1
On Saturday, March 26, 2011 4:27:10 PM UTC+1, Maaartin wrote:

IMHO, one of the most important uses of a builder is to replace long
argument list of constructors. Actually, it could be used to replace all
long argument lists. For other methods it's not as important as for
ctors, but thinking about a *builder for an argument list* instead of
for an object may itself be fruitful.


That's a subtle for very useful insight!

Unfortunately java doesn't (yet - JDK8 most likely will!) contain a mechanism to refer to methods or constructors. When thinking about this problem as: We need a way to create a builder which ends up calling any random method-like entity that needs to be passed a list of arguments, the seemingly simple solution is to do something like:

@BuilderFor(????)
public class FooBuilder {}

where ???? is a reference to some constructor or method. The problem is, how would one write up what one is targetting? This way all I can think of are strings, and very ugly ones at that. How do you use a string to differentiate between 2 constructors? We can kludge a little more like this:

@BuilderFor(type=Foo.class, constructor={String.class, List.class, String.class})

Refers to Foo's constructor that takes a String a List and a String. This is a unique way to identify things.

@BuilderFor(type=Foo.class, method="bar")

This refers to the method bar in type Foo.class, but as there's no way to differentiate it requires 'bar' to not be overloaded in Foo. I also get my 'ugly!' senses tingling when methods are referred to in a string literal, though lombok would of course at-write-time error on "bar" if "bar" did not exist (or was overloaded).

@BuilderFor(type=Foo.class, method="bar", args={String.class, List.class, String.class})

This is a reference to a method in Foo with signature bar(String a, List<whatever> b, String c).

Alternatively the annotation can be put on the item in question, i.e. just annotating that bar method with @Builder(?????), where ???? now becomes an issue of: Where should this builder be created. Perhaps by default it could be an inner class with in tandem a starting point method created at the same lexical level as the thing you put a Builder annotation on (i.e. a buildBar() method which returns a Foo.BarBuilder instance that has builder methods along with a .complete() method that calls bar with the args provided so far via the builder).
 
Both have their pros and cons so presumably we'd actually write both. Perhaps @Builder could take an optional string which becomes the name of BarBuilder; when written out like that you get a top-level class instead of an inner one. Though, I'm not sure we'd actually introduce that: What's the possible use case for such a construct? Perhaps the kick-off method is a more important thing to configure. "buildMethodName" by default, but you can change it if you want. A humongous advantage of this way is that we don't actually need resolution to build this take on @Builder. (@BuilderFor will always have to be a resolution dependent transformation, unfortunately).

Constructors can be treated like static methods, in that they do NOT need an instance to operate. Thus the buildX method for a static method or constructor is itself static, but the buildX method for an instance method with lots of args would be an instance method. For builders made via @BuilderFor for instance methods, the builder's constructor would take the instance.


This does not solve the additional problem of customizing the builder. For example, frequently builders where only 1 or 2 of the many args are mandatory require you to put those mandatory args in the constructor and then set up whatever else you need via builder calls. Possibly we can make a stand here and try to de-facto standardize on a simple universal pattern, which involves builders never taking a constructor argument and doing everything via builder methods, with the eventual build() call emitting a runtime exception if you forgot to set some mandatory things.

By generating a set of builders you can theoretically make a builder that will statically check you've called all mandatory methods, but this requires 1 fairly large class for EACH mandatory argument, so that can really spin out of control. Lombok would of course generate all of it under the hood, but I'm not sure if the advantages outweigh the complexity and disadvantages when doing this.The idea here would be to start with a T_ builder, and if you then use this to set mandatory field A, you get a T_A_ builder. When you now set optional field F you still get a T_A_ builder back, but if you then set mandatory field C you get a T_A_C_ builder. Only the T_A_B_C_D_ builder (assuming A, B, C, and D are mandatory) even has a complete() / build() / whateverYouWantToCallIt() method. The type name of the builder you get (which shows up in auto complete dialogs and such) is also going to look pretty nasty.

@NotNull MapEvictionListener evictionListener;

@CheckedBy(MyNonNegativityChecker.class) int maximumSize;

could work. Somehow. Otherwise the checks had to be postponed to the
ctor which wouldn't be fail-fast.

@NotNull is something lombok already understands, but @CheckedBy doesn't sound like a good plan. If you need a check for the maximumSize call, just write it! In your @BuilderFor annotation, manually stick:

 public BuilderTypeName maximumSize(int x) { if (x < 0) throw new IllegalArgumentException(...); this.maximumSize = maximumSize; return this;}

inside. Lombok sees it, skips generating this method, and all would be well.
 

There are also checks for not specifying a value twice, which may
capture some errors but limit the usability a bit. This should be
probably optional in a general builder.


I'm of the opinion not to make this optional. For a simple reason:

Either you're building in one big line and its relatively easy to see a double call to the same setter, so its hardly worth jumping through hoops for to give you a runtime error when you do this, _OR_, its the kind of builder that you pass around and in such cases being able to overwrite something a previous link in the chain set up, e.g. as default, is usually a _good_ thing.

In other words, those few builder APIs out there already that do this are being overzealous in their quest for purity. Besides, runtime purity is not worth nearly as much as compile time purity, and enforcing this at compile time is like the previous T_A_B_C_D_ builder thing except with 1 builder class for EVERY arg!

There are also some checks for combined conditions like not specifying
both expireAfterAccess and expireAfterWrite. I don't think this
could/should be automated at all. I wonder if there's a way for
combining such checks with lombok-generated code.


In practice most builders are called via chain-calls-then-build, all in one line. In such a case, throwing an exception at constructor is really barely worse than doing so earlier. 

Meta-programming in these checks via annotations is going to be way too convoluted. One option I could get behind is for @BuilderFor to look for a method named 'verify' which is called by every setter. This method can do a state inspection on all fields in the builder and throw the appropriate exception if something is wrong.

Reply all
Reply to author
Forward
0 new messages