3 ms·
As I read the post, I thought of relational data models. The behavior is expected. I believe the root issue is your records should not have computed fields tha
by wpollock 1y ago
As I read the post, I thought of relational data models. The behavior is expected. I believe the root issue is your records should not have computed fields that depend on mutable fields. Change your record schemas to eliminate that and you should have no further problems using "with".
If changing the schema isn't reasonable, use a copy constructor instead.
- louthy 1y ago> The behavior is expected It isn't, that's why there's a blog article documenting how unexpected it is. > that depend on mutable fields The fields are not mutable. The `with` expression creates a whole new record, clones the fields, and then sets the field you're changing (the field is read-only, so this is the compiler going 'behind the scenes' to update the new record before it sets the reference). The reason for all this is performance: the new structure is allocated on the heap, a memcopy happens (old structure copied onto the new), and then the `with` changes are applied. It's just at this point the 'init time' properties aren't run on the new object. In the language the fields are immutable. So, the argument is that a whole new record initialised with the fields of the old record (with some changes) should run the 'init time' properties so that they get set too, otherwise the data-structure can become inconsistent/poorly-defined. > use a copy constructor instead It's probably worth reading the article: "Note that because Value is set after the cloning operation, we couldn’t write a copy constructor to do the right thing here anyway."
- cbsmith 1y ago> It isn't, that's why there's a blog article documenting how unexpected it is. The behaviour is expected for the language design. Whether developers using the language expect it is a separate matter. The with operator clearly allows someone to break encapsulation and as such should only be used in cases where you aren't expecting encapsulation of the underlying record. > It's probably worth reading the article: > "Note that because Value is set after the cloning operation, we couldn’t write a copy constructor to do the right thing here anyway." It's probably worth reading the entire article, as that quote is followed by: "(At least, not in any sort of straightforward way – I’ll mention a convoluted approach later.)", which presumably was what was being referred to there. In general, there's a whole ton of gotchyas around encapsulation of precomputed values. That's just life outside of a purely functional programming context.
- louthy 1y ago> The behaviour is expected for the language design. So, Microsoft meant it. Ok... > Whether developers using the language expect it is a separate matter. Really? Perhaps read the 'Principle of Least Astonishment' [1] to see why this is a problem. If I create a new object I would expect the 'init time' properties to be initialised. > It's probably worth reading the entire article, as that quote is followed by: "(At least, not in any sort of straightforward way – I’ll mention a convoluted approach later.)", which presumably was what was being referred to there. It's probably worth continuing to read the article. Because the attempt to deal with it required manual writing of Lazy properties: private readonly Lazy<ComputedMembers> computed = new(() => new(Value), LazyThreadSafetyMode.ExecutionAndPublication); That's not practical. Might as well use computed properties. > In general, there's a whole ton of gotchyas around encapsulation of precomputed values. That's just life outside of a purely functional programming context. Great insight. Let's not run the 'init time' properties for a newly initialised object, just in case it works as expected. This 'feature' can't even be manually resolved by doing post-`with` updates (because often the properties are init/read-only). It makes the whole init-property feature brittle as fuck. [1] https://en.wikipedia.org/wiki/Principle_of_least_astonishment https://en.wikipedia.org/wiki/Principle_of_least_astonishmen...
- cbsmith 1y ago> Really? Perhaps read the 'Principle of Least Astonishment' [1] to see why this is a problem. If I create a new object I would expect the 'init time' properties to be initialised. The Principle of Least Astonishment definitely applies. It's a deliberate design choice that unfortunately violates the principle. > Great insight. Let's not run the 'init time' properties for a newly initialised object, just in case it works as expected. This 'feature' can't even be manually resolved by doing post-`with` updates (because often the properties are init/read-only). It makes the whole init-property feature brittle as fuck. ? I'm not sure I follow what you are going with here, but yeah, in general you'd have to carefully limit all uses of "with" for objects with precomputed values to inside the encapsulation of said objects. Alternatively, as you mentioned, you could just not have precomputed properties.
- deleted 1y ago[deleted]