Hacker News new | ask | show | jobs
by ivan_gammel 1249 days 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
1 comments

I can't find anything in the linked eg-draft that would indicate that the error would "occur later". In fact, it explicitly says:

    Note too that if the canonical constructor checks invariants, then a with expression will check them too. For example:
        record Rational(int num, int denom) {
            Rational {
                if (denom == 0)
                    throw new IllegalArgumentException("denom must not be zero");
            }
        }
    If we have a rational, and say
        r with { denom = 0; }
    we will get the same exception, since what this will do is unpack the numerator and denominator into mutable locals, mutate the denominator to zero, and then feed them back to the canonical constructor -- who will throw.
Please re-read the thread, the problem was described multiple times in it.