6 ms·
The biggest thing missing from records that Lombok provides is withers.
by wtetzner 4y ago
The 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[deleted]
- logicchains 4y agoThe proposal is meant for records, and records are used for "plain old data", explicitly without any encapsulation.
- ivan_gammel 4y agoNope. If records were structures, they would not be allowed to have methods or overloaded constructors or even default constructor. The most obvious example of encapsulation in records is validation of state.
- kaba0 4y agoThey 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.
- ivan_gammel 4y ago>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.
- plumarr 4y agoIt 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.
- ivan_gammel 4y ago>>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
- wtetzner 4y agoI don’t believe this is true. From my reading of the document, all with expressions go through the primary constructor.
- ivan_gammel 4y agoWell, that is exactly the problem, as I already explained a few times in this thread. Constructor cannot validate state transitions, so the “with” statement or the block surrounding it will have to do it, breaking encapsulation and likely requiring multiple copies of this code.
- wtetzner 4y agoBut if that’s the case, then a record is the wrong data type for what you’re doing anyway, since you can call the primary constructor at any point with those same un-validatable values. It’s not “with” that’s problematic. If a record doesn’t work for what you’re trying to do, just use a class. Though I will say that this is why I generally think ML’s (i.e. OCaml, Standard ML) approach to encapsulation with modules is generally superior to using classes for encapsulation.
- ivan_gammel 4y agoHave a look at the proposal. Half of it is dedicated to regular classes, which makes sense, because record is just a syntactic sugar in Java at the moment. Also this: https://news.ycombinator.com/item?id=34398982 https://news.ycombinator.com/item?id=34398982
- pron 4y agoWithers invoke the constructor. E.g. r = r with { state = state + C; }; compiles to: r = new R(r.state() + C); All state transitions of ADTs can be checked in constructors.
- ivan_gammel 4y agoState transition cannot be checked by constructor because it doesn’t know previous state.
- pron 4y agoAh, I see what you mean. It's not about that proposal, but about the fact that record constructors cannot be private (at least not for a public record). That's because records are meant as product types, or nominal tuples, which don't break encapsulation but exist to represent the notion of an unencapsulated tuple. That's their job: to be unencapsulated data with everything that enables. Now, you may ask why they don't also do other things, and I guess its possible that in the future we'll allow private record constructors, but there's less need for that, because Java already has a construct for encapsulated state -- ordinary classes.
- ivan_gammel 4y agoRegardless of what records were meant to be, they are designed in a way where they do offer encapsulation (which by definition means not only the state, but also the behavior of an object, and it does not mean that we always hide the state - only that we control the interface to it). Records can have business methods and can have non-trivial constructors, so they are basically a syntax sugar for special form of classes, not tuple structures. If you can use them to encapsulate certain forms of behavior, e.g. by declaring methods for state transitions, then with{} block will be a change in their interface. Do I expect that new version of language will change interfaces of my data structures? Hell, no! This feature must be designed as opt-in solution, following the example of Iterable and foreach loop and requiring explicit declaration of wither interface. What kind of declaration could that be? Imagine uniform declaration of intent for records and classes like in this example: public record Point with (int x, int y) {} public class Order { public Order() with (OrderStatus status, Instant timestamp) { … } public @(OrderStatus status, Instant timestamp) { … } } Here we explicitly say that Point generally supports „with“ block for all fields and Order supports deconstruction to status and timestamp and construction of a new object with the same fields. This way existing code retains the interface but can be easily modified to support the new syntax.
- dmitriid 4y agoStrange how you consider property assignments from C# but only in the very limited scope of `with`. Why not have all other lessons from C# like `{get; set;}` and object initialisation (among other things) syntax is beyond me. (Well, "we don't adopt strategies [whatever that means] from less successful languages" is a reason I guess https://news.ycombinator.com/item?id=28985688 https://news.ycombinator.com/item?id=28985688)
- pron 4y agoWhat I meant was that we don't adopt a feature just because some other language has it, let alone from languages that don't have the same philosophy as we do of trying to have as few features as we can get away with. Records are overall more valuable than properties (due to various reasons, from serialization to interaction with collections). But once you have records with "withers" the question becomes how valuable is it to also have properties. It doesn't seem like it would be helpful enough to overcome the drawbacks. BTW, C# didn't add properties as a "lesson." Properties in C# and JavaBeans have the same pedigree: RAD UI composer tools of the 90s [1]. They came to C# by way of VB. So really, Java would be adopting a VB solution, and there needs to be a good reason for that. [1]: https://en.wikipedia.org/wiki/Rapid_application_development https://en.wikipedia.org/wiki/Rapid_application_development
- dmitriid 4y ago> and there needs to be a good reason for that. A year ago I already said all the good reasons. There are many reasons why things like Lombock exist. There are reasons why people loathe writing interminable chains of builder methods. But sure. None of these are good reasons because something something Visual Basic. What Java will inevitably end up with is a yet another half-assed approach that only exists for a small part of the language and is not applicable to the rest of it. > BTW, C# didn't add properties as a "lesson." That's not what I wrote. This is literally from your link: "Digression: learning from C#" And look. Right below it, emphasis mine --- start quote --- The C# approach was sensible for them because they could build on features they already had (default parameters, properties) --- end quote --- And look. "Extrapolating from records" section basically says: Java has nothing, and will need to rebuild everything from scratch for this not to be a half-assed solution. Oh well.
- sverhagen 4y agoNot 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.