11 ms·
Making Lenses Practical in Java
- gardenfelder 4y agohttps://github.com/chriskiehl/Deoplice https://github.com/chriskiehl/Deoplice
- Twisol 4y agoFor want of context, this is the library described in the article.
- javaunsafe2019 4y agoI just use Kotlin. Never looked back.
- Garlef 4y agoSure. But what does that have to do with lenses?
- googlryas 4y agoHow do you update a deeply nested immutable type in kotlin?
- david_allison 4y agohttps://arrow-kt.io/docs/optics/lens/ https://arrow-kt.io/docs/optics/lens/
- ackfoobar 4y agoLenses work today. And I think the value class feature will be awesome. https://github.com/Kotlin/KEEP/blob/master/notes/value-classes.md#deep-mutations https://github.com/Kotlin/KEEP/blob/master/notes/value-class...
- jeroenhd 4y agoKotlin 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 problem is that mutated copies of immultable objects with deeply nested properties that need to be updated just requiresa lot of code. On the other hand, if I were to design the application in Kotlin, I wouldn't use such a deeply nested object graph if I was going to constantly duplicate theses objects. I also think constantly creating and destroying immutable objects is a sign of immutability being used in the wrong place; I don't know why you wouldn't simply mutate state if the goal is to mutate the state. With Kotlin, but also Java, you could then very easily achieve these changes. Lenses are available as a package for both Java and Kotlin so the concept is hardly language specific. People find a use for them in both languages, even if I don't see the point, and that means just switching to a better language isn't the solution here.
- brabel 4y agoI like to call this "direct code". It does exactly what it looks like it does. Lenses do not, they introduce a whole abstraction around something as simple as get/set. For this reason, the Kotlin code above would be much preferred IMO.
- jeroenhd 4y agoI agree, but the Kotlin code isn't that different from the Java code in the blog. The biggest difference is that Java lacks the necessary built-in support and relies on Lombok to provide the necessary copy helpers, but I can't think of a reason for a Java project not to use Lombok. I see Kotlin as better Java, but with the right compiler extensions (Lombok, Manifold) you can turn Java into a pretty modern language as well, if you're not writing legacy code in Java 8/9/11. The specific approach this article is about doesn't really put Kotlin at an advantage, nor does it place it at an advantage. You might as well write the Lens pattern in Python or Rust, the idea is still the same.
- tsimionescu 4y agoThe problem this is trying to resolve is creating copies of a complex immutable class with some deep fields changed. I do have to wonder for a case like this if using a mutable object with a deep copy function wouldn't be a better solution, rather than adding extra magic to do it more easily as possible... Edit: The code in the article would become something like pendingOrders.map(order -> { copy = order.DeepCopy(); copy.getApproval() .getConfirmation() .setUpdatedOn(LocalDateTime.now())); return copy; } ) (of course, we could also get rid of the getters and setters and replace them with direct field access, if we take the data class concept seriously)
- ImprobableTruth 4y agoThe issue is that this potentially copies a lot of unnecessary information. Also, if you depend on object identities (reference equality), deep copying will recreate objects even if they aren't supposed to change. Furthermore, if you want to chain two update functions, you'll deep copy parts of the object twice times. Lenses are "smart" in the sense that they only copy what's necessary.
- ivan_gammel 4y agoA business method on order would do the same without any unnecessary overhead and ensuring proper encapsulation and consistency of the state that cannot be achieved with getters and setters. pendingOrders.map(order -> order.approvalConfirmationUpdated(now())
- kitd 4y agoI believe the intention is to allow operations on multilayered business objects to be composable. So your approvalConfirmationUpdated() method may actually be made up of a sequence of smaller operations, each of which I might want to use elsewhere in the application. Lenses allow this to happen succinctly, and works better with eg the Java streams API.
- 4y ago
- _old_dude_ 4y agoI believe this is a case where a named tuple of functions, a record, is better than an interface. public record Lens<A, B>(Function<A, B> get, BiFunction<A, B, A> with) { public <C> Lens<A, C> compose(Lens<B, C> inner) { return new Lens<>( inner.get.compose(get), (a, c) -> with.apply(a, inner.with.apply(get.apply(a), c))); } } Lens<Confirmation, LocalDateTime> $updatedOn = new Lens<>(Confirmation::updateOn, Confirmation::updateOn);
- fedeb95 4y agoI 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.
- marginalia_nu 4y agoRecord classes arguably do a lot of the same thing without the drawbacks.
- wtetzner 4y agoThe biggest thing missing from records that Lombok provides is withers.
- pron 4y agoIn the works: https://github.com/openjdk/amber-docs/blob/master/eg-drafts/reconstruction-records-and-classes.md https://github.com/openjdk/amber-docs/blob/master/eg-drafts/...
- ivan_gammel 4y agoI hope they will find a better solution, as this proposal will break encapsulation. Let’s say you model allowed transitions via business methods and thus restrict certain state changes. You can either serialize or deserialize state or perform an allowed transition. If wither becomes a language feature, it will allow forbidden state changes that cannot be caught by validation in constructor, which will allow deserialization of states A, B, C, but will not know that A->C is forbidden, thus making the following possible: var r = new R(A); r = r with { state = C; } // passes, resulting in untraceable error much later where alternative could be: r = r.transitionTo(C); // throws exception
- deleted 4y ago
- ploppyploppy 4y agoJesus just use Kotlin.
- speed_spread 4y agoHaven't you heard about our new bytecode savior Scala3?
- deleted 4y ago[deleted]
- pron 4y agoLenses are cool, but they make me wonder: how many different paths of a deep immutable data hierarchy are transformed in an application to make this abstraction worthwhile, as opposed to replicating the entire traversal at each location? If the number is small then, however cool, this abstraction may be more trouble than it's worth. Just because you can reify some concept in an elegant composable construct doesn't mean that you should in the sense that it saves you a significant amount of effort. One way the number of paths could be (arbitrarily) large is if the transformations originate in user input, and the user can choose to arbitrarily transform any node in the structure. But then that input isn't typechecked (because it's not part of the program code), and requires interpretation and validation, so you might as well do the process of interpretation, validation, and transformation in one go using reflection. You don't lose the typechecking that you don't have in the first place, and the result is even more general. Now if you don't have reflection, lenses could make the interpretation of an input path much simpler (a switch of one level at a time), but Java does have reflection. So lenses solve a problem, but the question is: how big of a problem is it (especially in a language with reflection)?
- ivan_gammel 4y agoI’ve been playing with this idea around the time generics arrived in Java and never found a good use case for it. Number of transitions an object may have can usually be counted by fingers on one hand, meaning that you can have a method for each one and retain encapsulation, which lenses break by design.
- endgame 4y agoThe real power IMHO comes once you start using traversals, which are like lenses but focus 0..n elements instead of exactly 1, and compose well with themselves and with lenses. (It gets better still after that, as you add in Folds, Isos, Prisms, etc., but we'll leave those for now) Once you have a traversal that can pull out immediate children of the same type (e.g., "given a DOM node, traverse all of its children"), you can use a library of transformations like Haskell's Control.Lens.Plated module from package "lens" and write queries and transformations over arbitrary structures in a very compact way. I have used this a few times: some examples include walking complex documents to extract particular information from tables, or rewriting every "import" node in a syntax tree, but leaving the rest of the program untouched. To support Traversals in Java under that get/set form, you would probably need get/set members that were functions to/from T and Array<T>, and then you'd have to write separate compose operators for each composition: - lens + lens = lens - lens + traversal = traversal - traversal + lens = traversal - traversal + traversal = traversal This is one reason that Haskell's "lens" package has that funky type alias for Lens instead of a record-of-functions, and a mature lens library is one of the main reasons that Haskell is my favourite general-purpose programming language. You _can_ force such a take on optics into Java. Someone found an implementation of profunctor optics in Minecraft's DataFixerUpper: https://www.reddit.com/r/programming/comments/9lyplq/microsoft_open_sources_parts_of_minecraft_java/e7aids2/ https://www.reddit.com/r/programming/comments/9lyplq/microso... That subthread has some really good meaty comments in it, if you're interested in this sort of thing.
- vips7L 4y agoOr just stop trying to force immutability into something that is clearly mutable. You’re only causing unnecessary GC pressure by copying objects. Never the less this eerily looks similar to Goetz proposal for reconstructors: https://github.com/openjdk/amber-docs/blob/master/eg-drafts/reconstruction-records-and-classes.md https://github.com/openjdk/amber-docs/blob/master/eg-drafts/...
- deleted 4y ago[deleted]
- runeblaze 4y agoThe library (if I read correctly) seems to lack traversals, so no "each". Is it true that if I nest collections I am still in deep pain? Nonetheless still a cool library. The APIs in many cases look surprisingly natural.
- tpoacher 4y agoWho comes up with these names? Honestly.