Live data from Hacker News

Making Lenses Practical in Java

chriskiehl.com

101–108 of 108 posts

Re: Making Lenses Practical in Java

#101

Earlier quoted context omitted.

Well, 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.

But 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…

Have 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

Re: Making Lenses Practical in Java

#102
post #98

Earlier quoted context omitted.

I 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 deseriali…

Withers 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.

State transition cannot be checked by constructor because it doesn’t know previous state.

Re: Making Lenses Practical in Java

#103

Earlier quoted context omitted.

Imagine you changed it from an array of approvals to a single one like in the original example - you'd need to make a change in every method of that type (replacing the map). That's code duplication, it's just not really obvious yet because there's only two layers to pass through. Per layer you need a constructor call (or map to copy & modify the array). As a more obvious example, if you want to modify a.b.c.d.e (whi…

Those things are easy to imagine, however: 1) change in cardinality is such a change in domain that lenses won’t solve it. There will be much bigger changes in business logic probably making original code obsolete regardless of used pattern. 2) deep tree modifications of the kind that you mentioned indicate problems with architecture. Why would you need the whole typed tree (not DOM or something, but object tree) to…

[deleted]

Re: Making Lenses Practical in Java

#104

Earlier quoted context omitted.

Those things are easy to imagine, however: 1) change in cardinality is such a change in domain that lenses won’t solve it. There will be much bigger changes in business logic probably making original code obsolete regardless of used pattern. 2) deep tree modifications of the kind that you mentioned indicate problems with architecture. Why would you need the whole typed tree (not DOM or something, but object tree) to…

If you have an immutable structure, you can't just modify the leaf of it. Unless you're arguing that deep, immutable structures are an issue in themselves?

Oh, we cannot modify it, of course, but we are talking about the ways of producing a modified copy. The part of the argument about deep immutable structures is just a side remark: building them for modification is likely pointing to a problem with design, but that’s not the most important part. The important part is that it is better to call business methods than externalizing the whole thing with deconstruction-modification-construction to a lens or some other code, when you have the full power of OOP to do it right.

Re: Making Lenses Practical in Java

#105
post #98

Earlier quoted context omitted.

Withers 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.

State transition cannot be checked by constructor because it doesn’t know previous state.

Ah, 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.

Re: Making Lenses Practical in Java

#106
post #105

Earlier quoted context omitted.

State transition cannot be checked by constructor because it doesn’t know previous state.

Ah, 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…

Regardless 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.

Re: Making Lenses Practical in Java

#107
post #105

Earlier quoted context omitted.

Ah, 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…

Regardless 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 class…

> they are designed in a way where they do offer encapsulation

They are very intentionally designed to represent unencapsulated data. Records can have non-trivial constructors, but they all have a public canonical constructor, and while you can do strange things in your constructor and accessors (we needed that for technical reasons), the JEP/Javadoc/tutorials warn you against doing so, and that the reasonable assumption is that you don't.

The invariant is that if you have a record and deconstruct it using a deconstructing pattern, then you can also reconstruct it to get an object that's equal to the first by using the public canonical constructor. You can break that invariant, but libraries are allowed to assume that you don't.

> 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.

But you can't and so it won't. All (public) records have a public canonical constructor that you can use regardless of "state transition" methods, with or without withers. The relevant point, again, is not the "with" feature, but the publicness of the canonical constructor. You cannot limit the construction of a record to a state transition method even today, because you can't hide the canonical constructor.

There are certainly classes that do need private constructors, but if they do, then those classes are not records (we may expand the role of records in the future, but so far they're specifically designed to not allow that so that the reconstruction invariant is maintained).

> Imagine uniform declaration of intent for records and classes ...

There's no need to do that for records, because they have a public canonical constructor, and that is the constructor that's used by the feature.

Re: Making Lenses Practical in Java

#108

Earlier quoted context omitted.

I don’t think properties add anything that getters/setters don’t already give you (other than slightly different syntax). I think the “right” way to replace the need for builders is to add support for named arguments.

> I don’t think properties add anything that getters/setters don’t already give you Except that you have to write them out by hand for every property you want in your code. Or generate them. > I think the “right” way to replace the need for builders is to add support for named arguments. Often you want to add validators to your setters. Just named arguments won't cut it.

> Often you want to add validators to your setters. Just named arguments won't cut it.

Are you suggesting that arguments can’t be validated in a constructor? I don’t think I’m following the argument.

Post reply on HN