Feedback on reconstruction/'withers': Lombok lessons.
Remi Forax
forax at univ-mlv.fr
Sat Aug 15 09:54:51 UTC 2020
----- Mail original -----
> De: "Reinier Zwitserloot" <reinier at zwitserloot.com>
> À: "amber-dev" <amber-dev at openjdk.java.net>
> Envoyé: Samedi 15 Août 2020 01:51:25
> Objet: Feedback on reconstruction/'withers': Lombok lessons.
> In response to:
> https://github.com/openjdk/amber-docs/blob/master/eg-drafts/reconstruction-records-and-classes.md
>
> Lombok has had withers for a decade. Almost nobody uses them. However, take
> that with a grain of salt; wither has been in our experimental package for
> most of that time, and a significant chunk of lombok users won't use
> features from that package, vs. its closest competitors (@Builder, and
> having a mutable type instead) which have been in the main package for
> (almost) their entire lifetime. Then again, we got very few requests to
> upgrade Wither to core support status (we did upgrade it last year, but
> mostly because we wanted to, and not due to much community demand; other
> experimental features get far more requests to get upgraded to core support
> levels).
>
> Contrast to `@Data` (makes setters and getters, i.e. solve the problem by
> making your type mutable, which is not a direction that records want to go
> to, obviously), and it's no contest: @Data is many orders of magnitude more
> popular.
>
> Even `@Builder` is an order of magnitude or two more 'popular' than @Wither.
>
> I like withers. However, You can both get rid of them, and solve the
> combinatorial explosion problem, if instead you allow records to
> automatically make you a builder, _and_ if you can turn an existing record
> instance into a builder too, that acts a lot like withers do (looking ahead
> to valhalla, if we have a valhalla codes-like-an-class-performs-like-an-int
> class, is 'instance' still the right word? I digress).
>
> Consider a Bridge class that has a few properties, including 'name',
> 'buildYear', and 'length':
>
> Bridge goldenGate = Bridge.builder()
> .name("Golden Gate")
> .buildYear(1937)
> .length(8980)
> .build();
>
> Assuming Bridge is immutable here, I need to correct my mistake; the length
> should obviously be in meters, not feet. Lombok offers two different
> options:
>
> * annotate the `length` field with a `@With` annotation (we very recently
> renamed it from `@Wither` to `@With`), then:
>
> goldenGate = goldenGate.withLength(2737);
>
> * allow builder to also build off of instances ( via @Builder(toBuilder =
> true)):
>
> goldenGate = goldenGate.toBuilder().length(2737).build();
>
> contrasting these two options:
>
> * the toBuilder route is a lot of syntax especially if you want to modify
> only one property.
> * toBuilder always makes exactly 2 objects (the builder, and the new
> instance), regardless of how many properties you modify. the with methods
> will create 1 object per with invocation. If you update 4 properties, you
> get 3 intermediate garbage objects, and one final one you actually wanted.
>
> I can see ways of eliminating the builder-route's intermediate object. I'm
> not sure it's particularly performance relevant for the base records case,
> but once you toss valhalla into the mix, that builder object is probably
> not palatable.
>
> builders have the exact same* memory load as the base classes do: One for
> each field; one could imagine a system where a builder is turned into the
> object it is trying to build simply by overwriting those parts in heap
> memory that represent 'what type am I', leaving the actual field data
> unmolested. With builders, the problem is, someone may still hold a
> reference to the builder which would violate heap rules, but with syntax
> sugar, you can ensure that no code could possibly have a ref to the builder
> (which has now magically turned into the object it was building). This
> concept could then extend to valhalla types in time (where, presumably, the
> "fields" are all on-stack).
>
> That just leaves the syntax which is a bit unwieldy. That too seems
> fixable, this time with some sugar: (<insert usual Brian strawman syntax
> disclaimer here>):
>
> Bridge goldenGate = Bridge.build {
> name = "Golden Gate",
> buildYear = 1937,
> length = 8980,
> };
>
> (I would strongly advise that trailing commas are allowed, just like they
> are in array decls and enum decls).
>
> and 'build from instance' could then become:
>
> Bridge fixedGoldenGate = goldenGate.with { length = 2737 };
>
> all that being syntax sugar (as in, there still is a builder() method, and
> it has a build() method - the above is just syntax sugar to invoke
> them, although with the sugar you may get the benefit of getting the
> builder instance optimized out). This also gives the benefit that if you
> want to make a builder, fill in half of the fields, then pass the builder
> itself off to a helper method, you can do that (just don't use the syntax
> sugar), whereas with the with{} concept that becomes a little harder,
> perhaps.
>
> --Reinier Zwitserloot
>From my own experience, Lombok is mostly used with hibernate, JPA, etc which require classes to be mutable, so it doesn't seem odd to me that withers are not used to lot.
Then you're asking for a way to create a record with is key based syntax and not positional based syntax, it does not have to be a builder, just another way to call the canonical constructor,
we (the amber EG) are on it, that why when you define the canonical constructor of a record you can not use the parameter names you want.
Rémi
More information about the amber-dev
mailing list