Live data from Hacker News

Making Lenses Practical in Java

chriskiehl.com

61–70 of 108 posts

Re: Making Lenses Practical in Java

#61
post #9

I don't agree on the fact that lombok has brought us out of the dark ages. We used to use it, but it has some drawbacks. One of them, it's an additional dependency. This, for a simple thing such as pojo, seems a bit overkill to me. The additional amount of time used to write some cose isn't worth the risk of additional bugs hidden in having one more dependency.

With Lombok I always felt that it was trying to fix a symptom while it ignored the underlying problem. If you don't like to write out getters and setters in your code, then consider not to implement them at all. Just use value classes with public fields. Or nowadays you can also use records.

Some people use the argument that the advantage of getters/setters is, that you can add custom logic to them. But IMO that is an antipattern. And even more so if you then hide that enriched setter or getter using Lombok. Code should indicate to the readers what is doing, it shouldn't try to hide stuff that could be important.

Re: Making Lenses Practical in Java

#62
post #9

I don't agree on the fact that lombok has brought us out of the dark ages. We used to use it, but it has some drawbacks. One of them, it's an additional dependency. This, for a simple thing such as pojo, seems a bit overkill to me. The additional amount of time used to write some cose isn't worth the risk of additional bugs hidden in having one more dependency.

I never particularly liked Lombok either but I do appreciate what it does. Which is to remove a lot of boilerplate code related to setters, getters, hashcode, and equals functions, builder classes, etc. Writing that kind of boiler plate manually increases the likelihood of bugs, inconsistencies and allows other weirdness to creep into your code base. Also it's rather stupid work to do.

These days, I use Kotlin, which removes the need for Lombok while still being able to play nice with it. The most recent version of Kotlin actually added a compiler plugin for Lombok annotations that makes it easier for people with legacy Java code bases with Lombok to introduce Kotlin to their code bases.

Kotlin doesn't have direct support for lenses but it is a pretty popular feature with some frameworks. Arrow has an implementation for example. And Arrow is of course inspired by Scala. I've also used the Fritz2 framework for browser UIs. It uses compile time generation of lenses. I think they are a bit of a double edged sword. Looks like a lot of complexity for not a whole lot of gain to me. I like keeping things simple instead. This stuff does have the distinct taste of over engineering to it.

Re: Making Lenses Practical in Java

#63
post #9

I don't agree on the fact that lombok has brought us out of the dark ages. We used to use it, but it has some drawbacks. One of them, it's an additional dependency. This, for a simple thing such as pojo, seems a bit overkill to me. The additional amount of time used to write some cose isn't worth the risk of additional bugs hidden in having one more dependency.

With Lombok I always felt that it was trying to fix a symptom while it ignored the underlying problem. If you don't like to write out getters and setters in your code, then consider not to implement them at all. Just use value classes with public fields. Or nowadays you can also use records. Some people use the argument that the advantage of getters/setters is, that you can add custom logic to them. But IMO that is a…

I feel that when I programmed in C++ before I went to Java, it was much more common to include more logic in your setters than just assigning the argument to the field. This is now almost twenty years ago, I was less experienced and the team I was on where I was writing C++ was small and inexperienced too, so maybe that's a misconception. But certainly, in Java the additional logic in setters is frowned upon; but so are public fields. So, it's really a cultural thing in that community. Thank goodness for records :)

Re: Making Lenses Practical in Java

#64

Earlier quoted context omitted.

Record classes arguably do a lot of the same thing without the drawbacks.

The biggest thing missing from records that Lombok provides is withers.

Not that I want to switch out one compiler plugin for the other, but Immutables gives you withers, if I'm not mistaken. I've not been much into withers, until today that is. But for everything else, Immutables has been doing a pretty decent job at my work outfits.

Re: Making Lenses Practical in Java

#65
post #9

I don't agree on the fact that lombok has brought us out of the dark ages. We used to use it, but it has some drawbacks. One of them, it's an additional dependency. This, for a simple thing such as pojo, seems a bit overkill to me. The additional amount of time used to write some cose isn't worth the risk of additional bugs hidden in having one more dependency.

The risk of bugs because you forgot to update a setter after changing a field or something like this is way higher than the risk of bugs in Lombok.

Re: Making Lenses Practical in Java

#66

I just use Kotlin. Never looked back.

Kotlin alone won't implement these features for you. You'd end up with something like this: val order = loadOrder() val approved = order.copy( approval = order.approval.copy( confirmation = order.approval.confirmation.copy(updatedOn = Instant.now()), status = ApprovalStatus.Approved ) I think I prefer this explicit deep cloning over the automagically generated lenses in standard code, but I can see the appeal. The pr…

For the example provided I would encapsulate that logic in the object itself returning a confirmed copy as a return. And as someone else pointed out I also much prefer explicit code where you easily can understand what is going on…

Re: Making Lenses Practical in Java

#67
post #58

Earlier quoted context omitted.

They still don’t have encapsulation, all members are readable on purpose. Withers will likely go through the same default constructor so constraints can be upheld, though.

>They still don’t have encapsulation, all members are readable on purpose. Public state does not mean no encapsulation. The purpose of encapsulation is to bundle state and behavior and hide them both behind an interface, but that interface can offer read access to state. The key here is behavior. >constraints can be upheld My example above demonstrates a constraint that cannot be implemented in a constructor.

It can't but that's not due to the proposed solution for withers.

The cause is Records themselves. Record are immutable on purpose and you want to add a mutation constraint whish will not play nicely.

The proposed constraint will be triggered in this case :

    var r = retrieveFromDb(); (value is A)
    r = r.with(C) -> throw an exception
But this only work if B is generated by the wither. If I deconstruct the record manually and reconstruct with C manually, it'll work. So this offer no garantee that this transition will never occur.

I would even argue that this constraint can't be implemented at the level of the class, at least if the class is only a data carrier without external dependencies.

Re: Making Lenses Practical in Java

#68
post #33

Earlier quoted context omitted.

JDK 8 is now a minority, and if we count only projects that are still undergoing significant development it's a significantly smaller minority. The main reason some projects are still stuck on 8 is because they use old libraries that have made breaking changes, and don't have resources to adapt their use, let alone take on new ones.

The way I heard it, Java 9 breaks Spark 2.4 badly with no good workaround to disable Jigsaw (which we otherwise don’t care about), and that’s is a problem for a lot of data pipelines in our monorepo. It sounds like other large projects also had a lot of trouble working around Jigsaw, especially for proxying and mocking, and you are blocked at least as long as any of your dependencies are blocked (which was also one r…

A lot of code broke on Java 9, but that had little to do with Jigsaw (accessibility remained unchanged from 8 until JDK 16). Code broke because it was not portable and relied on internal implementation details that then changed. Still, Java 8 is now a minority (most applications had to upgrade their non-portable dependencies to new versions).

Re: Making Lenses Practical in Java

#69
post #45
post #33

Earlier quoted context omitted.

JDK 8 is now a minority, and if we count only projects that are still undergoing significant development it's a significantly smaller minority. The main reason some projects are still stuck on 8 is because they use old libraries that have made breaking changes, and don't have resources to adapt their use, let alone take on new ones.

Or they develop for Android. Not a minority.

Android isn't Java, but at less than 1/4 the size of the Java ecosystem, even if it were Java it would still be a minority.

Re: Making Lenses Practical in Java

#70
post #67

Earlier quoted context omitted.

>They still don’t have encapsulation, all members are readable on purpose. Public state does not mean no encapsulation. The purpose of encapsulation is to bundle state and behavior and hide them both behind an interface, but that interface can offer read access to state. The key here is behavior. >constraints can be upheld My example above demonstrates a constraint that cannot be implemented in a constructor.

It can't but that's not due to the proposed solution for withers. The cause is Records themselves. Record are immutable on purpose and you want to add a mutation constraint whish will not play nicely. The proposed constraint will be triggered in this case : var r = retrieveFromDb(); (value is A) r = r.with(C) -> throw an exception But this only work if B is generated by the wither. If I deconstruct the record manuall…

>>My example above demonstrates a constraint that cannot be implemented in a constructor.

>It can't but that's not due to the proposed solution for withers.

This has nothing to do with withers. It can't be implemented simply because it is a constraint on a specific transition of state. There's no transition of state in constructor.

>Record are immutable on purpose and you want to add a mutation constraint whish will not play nicely.

The purpose of immutability is not to create constant objects, but to prevent side effects from sharing mutable objects: state modifications are possible, they are simply reflected in a modified copy of object. That also means that my argument stands also for entities modeled as classes with final fields, it has nothing to do with specifics of records.

Indeed, the fact that we have a constructor from which we can build any valid state and that we can deconstruct an immutable object means that we can bypass transition validations, but that will require some extra effort from developer and explicit demonstration of intent compared to simply using `with` block. Compare this:

  var rA = new R(I1, I2, I3, A); // deserialization, e.g. from persistent state
  
  // verbose, explicit intent to create a copy in state C
  var rC = new R(rA.i1(), rA.i2(), rA.i3(), C); // error occurs later
this:

  // no semantics, no validation
  var rC = rA with { state = C; } // error occurs later
and this:

  // clear semantics, validation of transition
  var rC = rA.onSomethingHappened(C); // exception thrown now
Post reply on HN