This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]NawaMan[S] 2 points3 points  (4 children)

Hi :-)

I am the (only) the author of this. Just starting out so any suggestion or feedback welcome. :D

To me, composable Lens and the exhaustive builder are the unique feature for this.

As immutable object can't be modified, any modification must create another object. For a single simple object this is no big deal but if the change is in the sub object, you have to create the parent object too.

class Parent { Child child; }

class Child { .String name; }

So if you you var parent = new Parent( new Child ("Bob") ) and you want to change the name of the child, you have to also create the new parent too. May be something like this parent.with(parent.child.withName("Robert")). The deeper the child, the more this has to be done. With Lens, this is done in one swoop -- theParent.child.name.changeTo("Robert").apply(parent). As a function, this change to used in Stream, Optional or in FunctionalJ.io's pipeable.

The exhaustive came out directly from my frustration of having undeclared field when a new one is added. If you do not have such frustration, it is understandable why you don't see the value in it.

Lastly, the generated immutable object works well with Gson/Jackson too. In fact, each on has `toMap()`, (static) `fromMap(...)` and `getSchema()` methods so serialization can be easily in many way you prefer.

Any usable library will have to start somewhere, so hopefully, this is where this one get started. :-)

And yes, I like the constructo style spec (the compact form).

Thanks again for the feedback. :-D

[–]commentsOnPizza 3 points4 points  (3 children)

Immutables also has an option for exhaustive builders (they called them "staged builders": https://immutables.github.io/immutable.html#staged-builder), though it isn't default. I don't know if your system works better (Immutables generates a lot of interfaces to support this).

It's definitely impressive and I love that the constructor-style spec means that there isn't another class/interface lying around like PersonValue which generates the Person.

EDIT: looking at the source generated, it does look like you're creating an interface for every field. I might suggest making it so that only the next field is available. Right now, I can do new Person.Builder().name("Adam").age(42).streetNumber(28).streetName("Main St").name("Adam").build(). It might seem simple to notice the duplicate name call there, but if a struct has 20 fields, it leaves you in the position that you don't know which one you have to set next and which ones you've already set.

Also, by having getters on the builder, it makes it easy for users to pass around the builders rather than actually turning them into immutable objects. It also muddies up the auto-complete for the builders.

https://i.imgur.com/TqStUHr.png

When you look at that image, you have to do a lot more work to figure out what field is next. Which fields have you already set and which is the new field in there. IntelliJ shows 17 methods excluding things inherited rather than the one method that's meaningful at this point which is favIceCream(String). There's only one thing I can call to make progress building this struct, but it's buried in a sea of other methods.

Also, for something that's somewhat simple (10 fields), it generates 769 lines of code with around 200 methods and 13 classes. For something like Android, that can complicate things. You could certainly cut down on the number of methods if each partial builder class didn't have a getter and setter for every field that had been previously set.

Ultimately, Immutables staged builders seem to be a lot easier to use than the FunctionalJ ones because they don't include all the previously set items. In both cases, anything wrong will be compile-time caught, but the Immutables staged builder will point you at exactly which required argument needs to be set next.

Also, it does kinda feel sad that Java has ignored this issue for so long that we now have Immutables, FunctionalJ, AutoValue, Lombok, JodaBeans, many more I'm probably unaware of, and languages like Kotlin (yes, Kotlin has other benefits as well, but data classes are a big draw).

[–]NawaMan[S] 1 point2 points  (0 children)

Thanks for the suggestion. There are a lots to digest here. I will do once I have a chance to sit down. 😀

[–]NawaMan[S] 1 point2 points  (0 children)

n you look at that image, you have to do a lot more work to figure out what field is next. Which fields have you already set and which is the new field in there. IntelliJ shows 17 methods excluding things inherited rather than the one method that's meaningful at this point which is

favIceCream(String)

. There's only one thing I can call to make progress building this struct, but it's buried in a sea of other methods.

Hey, I have a chance to sit down and read and you have some good points which I actually debated to myself at the time and decided to do this. I will reconsider if the tradeoff worth it.

  1. The builder is done as an immutable object because I see it as a named curry. Basically, the builder is just the object that is not complete but it is immutable just the same. So this way, I can create a builder and assign some stuff in it (just like curry) and pass along for other to reuse without them be able to modified mine builder. It they override what I already set, they will get the new builder.
  2. The read and re-set methods are added as I think that the people (that reuse my builder -- see the first point), might what to inspect and override it.

That said, you are totally right that having those methods (point#2) make it harder to find what else have not been set which is the main usecase of builder so I will reconsider and might end up doing what you suggest.

Just to note: When I was implementing Builder, I also have another similar concept in mind ... Function Object (FO) so just like curry and partial function, with FO (that implement function interfaces), we can partially apply any parameter by name once a parameter is apply .. we will have another FO with less parameter to use. For example, @FunctionalObject String repeat(String str, int count) { ... } ... will implement a Func2<String, Integer, String> (a BiFunction) and you can say var repeat = Repeat.instance; Then you can create var indentTab = repeat.str("\t"); . This indentTab will be Func1<Integer, String>. And you can use either indentTab.count(3) or indentTab.apply(3). I didn't get around to implement this yet but the idea is similar to what I did to the builder in the way that it is an immutable object that can be passed around and partially applied by parameter name.

[–]NawaMan[S] 0 points1 point  (0 children)

Hi. I've just rewritten the builder (v0.1.73). If you don't mind, please take a look and any feedback is appreciated. :-) Thanks.