<div dir="ltr"><div>Hi Gavin,</div><div><br></div><div>My response is mostly just to add grist to the mill for a feature that looks great already. You might perhaps feel some of the points are worth working into the proposal.</div><div><br></div><div>I have some angst over the term "derived" for this. It's not wrong, but is an entirely different meaning from the one I encounter regularly: a "derived field" being one that caches a value computed deterministically from the other field values (a feature that record classes notably don't support... sadly).</div><div><br></div><div>I think the more basic term is "modified", and I think it works: "creating modified records". In the vernacular I think most people do understand that a genetically "modified" soybean doesn't necessarily mean that any particular bean was changed, only that it is an altered version of what it would otherwise have been. "You can't <i>modify</i> a record instance, but you can get a <i>modified</i> instance based on it." This feels to me like a good reuse of existing terminology.</div><div><br></div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div>Suppose we want to evolve the state by doubling the x coordinate of a Point oldLoc, resulting in Point newLoc:<br><br>Point newLoc = new Point(oldLoc.x()*2, oldLoc.y(), oldLoc.z());<br>This code, while straightforward, is laborious. Deriving newLoc from oldLoc means extracting every component of oldLoc, whether it changes or not, and providing a value for every component of newLoc, even if unchanged from oldLoc. It would be a constant tax on productivity if developers had to repeatedly deconstruct one record value (extract all its components) in order to instantiate a new record value with mostly the same components.</div></blockquote><div><br></div><div>It's also bug-prone in multiple ways.</div><div><br></div><div>It also is the worst-case maintenance scenario, the "any-every". When adding, removing, or renaming <i>any</i> record component, <i>every</i> statement like this throughout the codebase has to be changed. (True of record constructor calls too, but there's not much we could do about that short of optional parameters.)</div><div><br></div><div></div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div>However, wither methods have two problems. First, they add boilerplate to the record class, </div></blockquote><div><br></div><div>Some boilerplate is relatively innocuous but this is <i>high-maintenance</i> boilerplate, which we have to carefully keep in sync with the record's component declarations. Boo.</div><div><br></div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div>Record values can be nested, where components are themselves record values. Derived instance creation expressions can be nested in order to transform nested record values. For example:<br><br>record Marker(Point loc, String label, Icon icon) { }<br><br>Marker m = new Marker(new Point(...), ..., ...);<br>Marker scaled = m with { loc = loc with { x *= 2; y *= 2; z *= 2; }};<br></div></blockquote><div><br></div><div>In fact, this is such a common need (in my experience), and what you have to do today is such a horror show, that you might want to illustrate it as part of the value proposition of the feature.</div><div><br></div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div>Derived instance creation expressions can be used in record classes to simplify the implementation of basic operations. For example:<br><br>record Complex(double re, double im) {<br>    Complex conjugate() { return this with { im = -im; }; }<br>    Complex realOnly()  { return this with { im = 0; }; }<br>    Complex imOnly()    { return this with { re = 0; }; }<br>}<br></div></blockquote><div><br></div><div>This is very nice because now `conjugate()` has no relationship with `re` at all, just as it should be. And it makes the essence of what each method is for crystal-clear.</div><div><br></div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div>Any assignment statements that occur within the transformation block have the following constraint: If the left-hand side of the assignment is an unqualified name, that name must be either (i) the name of a local component variable, or (ii) the name of a local variable that is declared explicitly in the transformation block.<br></div></blockquote><div><br></div><div>And because there's no way to qualify a local variable from the surrounding scope, reassigning such variables is simply impossible within this block. Right?</div><div><br></div><div>That's "no great loss" of course, although I'm missing why the restriction is necessary. The notion of variables that (at least in userspeak) are "in scope for reading but not for writing" seems weird; does it have precedent?</div><div><br></div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div>The transformation block need only express the parts of the state being modified. If the transformation block is empty then the result of the derived instance creation expression is a copy of the value of the origin expression (the expression on the left-hand side).<br></div></blockquote><div><br></div><div>This could be interpreted as saying that in this case the record's constructor isn't even run, which I suspect isn't what you mean, and which could make a difference (if best practices aren't being followed). Do you need to say anything at all about this case?</div><div><br></div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div>If the origin value is null then evaluation of the derived instance creation expression completes abruptly with a NullPointerException.<br></div></blockquote><div><br></div><div>... and if it isn't, then we can talk about the "origin instance" it refers to.</div><div><br></div><div>I'd suggest avoiding the term "origin value" completely except for the above, preferring to talk about the origin instance instead. I think that's the way to be as clear as possible that none of what we're talking about here cares whether the record class is a value class or not. But we can dissect this further if need be.</div><div><br></div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div>Before executing the contents of the transformation block, a number of implicit local variable declaration statements are executed. These local variable declaration statements are derived from each record component in the header of the record class R, in order, as follows:<br><br>The local variable declaration has the same name and declared type as the record component.<br></div></blockquote><div><br></div><div>Overall, there have been several references here to the record class R, but I would think it's the record <i>type</i> we really need to talk about. That type post-substitution is what determines these variable types, no?</div><div><br></div><div>That also suggests we need to discuss wildcard capture here - or is that addressed elsewhere?</div><div> </div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div>A new instance of record class R is created as if by evaluating a new class instance creation expression (new) with the compile-time type of the origin expression and an argument list containing the local component variables, if any, in the order that they appear in the header of record class R.</div></blockquote><div><br></div><div>Likewise, should this talk about type arguments too? This would I think mean duplicating them from the record type but doing whatever fancy footwork is required to deal with wildcards? (I assume that record constructors themselves can't be generic.)</div><div><br></div><div>Implied in all this: I would think a record type like `MyRecord<String, ?>` *should* be usable with `with` (of course, trying to assign to some variables inside the transformation block isn't going to go well, but likely the user just isn't referring to those variables at all in this case).</div><div><br></div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div>The use of a derived instance creation expression:<br>can be thought of a switch expression:<br></div></blockquote><div><br></div><div>imho this would be useful to state earlier!</div><div><br></div><div>What goes wrong if we think of this feature as <i>exactly</i> desugaring to that switch code?</div><div><br></div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div>The structure and behavior of the transformation block in a derived instance creation expression is similar to the body of a compact constructor in a record class. Both have the same control flow restrictions (must complete normally or throw an exception); both have a set of pre-initialized variables in scope, which are expected to be mutated by the block; and both take the final values of those variables and pass them as arguments to a constructor invocation.<br></div></blockquote><div><br></div><div>This would've been useful to state earlier too, to me anyway. The only difference I thought of is that one can refer to `this` inside the constructor (uh, right?) but there is no syntax to access the origin expression in the transformation block. And that seems as it should be.</div><div><br></div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div>Alternatives<br>Instead of supporting an expression form for use-site creation of new record values, we could support it at the declaration site with some form of special support for wither methods. We prefer the flexibility of use-site creation, whereas declaring wither methods would add bloat to record class declarations, which currently enjoy a high degree of succinctness.<br></div></blockquote><div><br></div><div>This would also introduce a lot of potential for unpredictability. The whole deal with records is that they act in highly predictable ways.</div><div><br></div><div>~~</div><div><br></div><div>Nano-scale details aside... this will be a very helpful feature for working with records and I hope it happens!</div><div><br></div></div>