<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;" class="">On May 23, 2020, at 9:07 AM, Remi Forax <<a href="mailto:forax@univ-mlv.fr" class="">forax@univ-mlv.fr</a>> wrote:<br class=""><div><blockquote type="cite" class=""><br class="Apple-interchange-newline"><div class=""><div class="Singleton"><blockquote type="cite" style="font-family: Literata; font-size: 18px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; text-decoration: none;" class=""><br class="">3. Record components could also be normalized in the constructor. E.g.<br class="">assume record Fraction(int numerator, int denominator) that normalizes<br class="">the components using GCD in the constructor. Using withers would<br class="">produce a weird result based on the previous value.<br class=""></blockquote><br style="caret-color: rgb(0, 0, 0); font-family: Literata; font-size: 18px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration: none;" class=""><span style="caret-color: rgb(0, 0, 0); font-family: Literata; font-size: 18px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration: none; float: none; display: inline !important;" class="">A record is transparent (same API externally and internally) so as a user i expect that if i set a record component to 3 using a constructor and then ask its value, the result will be 3,</span><br style="caret-color: rgb(0, 0, 0); font-family: Literata; font-size: 18px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration: none;" class=""></div></div></blockquote><div><br class=""></div><div>This is not the expectation we set for record constructors, so it is</div><div>debatable that it would be an expectation for record “withers”.</div><div><br class=""></div><div>This is why I like to use a special term for “withers”:  If an object</div><div>can be built from scratch by calling its class’s constructor, then</div><div>it follows that an API point which takes an existing record and</div><div>builds a new one (with modifications) is a sort of constructor</div><div>also; I call it a “reconstructor”.</div><div><br class=""></div><div>Some context: I’ve been thinking about this for a long time.</div><div>When we added non-static final variables, with their funny DU/DA</div><div>rules, I did the design and implementation, and one thing that</div><div>irritated me was the lack of a way to change just one or two</div><div>final fields of an object full of finals.  At the time final fields</div><div>were rare so we thought the user would always prefer to build</div><div>another object “from scratch”.  And it would be expensive enough,</div><div>since fast GC was not yet a thing, that the user would not want a</div><div>slick way to do the job.  Now with inline types the balance of</div><div>concerns has shifted, towards objects with *only* final fields,</div><div>and very low allocation costs, yet we still have a “hole” in our</div><div>language, corresponding to that old and now-important</div><div>technical debt.  Back in the ‘90s I didn’t have a crisp idea about</div><div>the shape of this technical debt, but now I think I do.  Which</div><div>is not to say that I have a crisp idea how to pay it off, but I have</div><div>some insights I want to share.</div><div class=""><br class=""></div><div>The debt shows up when we transform mutable iterators into</div><div>immutable value-based “cursors”.  For those we need a way to say</div><div>“offset++” inside an active cursor—yielding a new cursor value which</div><div>is the logical “next state” of the iteration.  Physically, that’s a</div><div>constructor for the cursor class which takes the old cursor and does</div><div>“offset++” (or the equivalent) in the body of that constructor.  Because</div><div>it takes the old cursor and preserves all other fields untouched, it is a</div><div>very special kind of constructor, which should be called “reconstructor”</div><div>because it reconstructs (a copy of) the old object to match some</div><div>new modeling condition (an incremented offset).  As side note, it</div><div>is telling that the rules of constructors (but no other methods)</div><div>allow you to perform local side effects on variables which</div><div>correspond to fields; of course you commit “this.offset = offset”</div><div>at some point and shouldn’t dream of modifying “this.offset”,</div><div>but the constructor is free to change the values before committing</div><div>them.  Compact record constructors make this more seamless.</div><div>In such settings (which are natural to reconstructors), saying</div><div>something like “offset++” or “offset -= adjust”, before commit,</div><div>is totally normal.</div><div><br class=""></div><div>A reconstructor makes the most sense for an inline type, but it</div><div>also makes sense for identity types (as long as users are willing</div><div>to eat the cost of making a new version of the object instead of</div><div>side-effecting the old version).</div><div><br class=""></div><blockquote type="cite" class=""><div class=""><div class="Singleton"><span style="caret-color: rgb(0, 0, 0); font-family: Literata; font-size: 18px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration: none; float: none; display: inline !important;" class="">so i don't think normalizing values of a record in the constructor is a good idea.</span><br style="caret-color: rgb(0, 0, 0); font-family: Literata; font-size: 18px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration: none;" class=""><span style="caret-color: rgb(0, 0, 0); font-family: Literata; font-size: 18px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration: none; float: none; display: inline !important;" class="">This issue is independent of with/copy, calling a constructor with the results of some accessors of an already constructed gcd will produce weird results.<span class="Apple-converted-space"> </span></span><br style="caret-color: rgb(0, 0, 0); font-family: Literata; font-size: 18px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration: none;" class=""></div></div></blockquote><div><br class=""></div><div>Calling it a reconstructor sets expectation that those results are</div><div>not weird at all:  You are just getting the usual constructor logic</div><div>for that particular class, which enforces all of its invariants.</div><div><br class=""></div><div>Having a “wither” that can “poke” any new value unchecked</div><div>into an already-checked configuration, without allowing the class</div><div>to validate the new data, would be the “weird result” in this case.</div><div>Let’s not do that, and let’s not set that kind of expectation.</div><div>Encapsulation means never having anyone else tell you the</div><div>exact values of your fields.  Constructors are the gatekeepers</div><div>of that encapsulation.  Even record classes (transparent as they</div><div>are) are allowed to have opinions about valid and invalid field</div><div>values, and to reject or modify requests to create instances</div><div>which are invalid according to the contract of the record class.</div><br class=""><blockquote type="cite" class=""><div class=""><div class="Singleton"><br style="caret-color: rgb(0, 0, 0); font-family: Literata; font-size: 18px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration: none;" class=""><blockquote type="cite" style="font-family: Literata; font-size: 18px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; text-decoration: none;" class=""><br class="">4. Points 2 and 3 may lead to the conclusion that not every record<br class="">actually needs copying. In fact, I believe, only a few of them would<br class="">need it. Adding them automatically would pollute the API and people<br class="">may accidentally use them. I believe, if any automatic copying<br class="">mechanism will be added, it should be explicitly enabled for specific<br class="">records.<br class=""></blockquote></div></div></blockquote><div><br class=""></div><div>An explicitly declared reconstructor would fulfill this goal.</div><div><br class=""></div><div>A reconstructor, as opposed to a “wither” feature, would also scale</div><div>from one argument to any number of arguments.</div><div><br class=""></div><div>This leads to the issue of defining API points which are polymorphic</div><div>across collections of (statically determined) fields.  Which is the</div><div>present point.  Note, however, that it has surprisingly deep roots.</div><div>As soon as we added non-static final fields to Java, we incurred a</div><div>debt to eventually examine this problem.  Time’s up; here we are.</div><br class=""><blockquote type="cite" class=""><div class=""><div class="Singleton"><span style="caret-color: rgb(0, 0, 0); font-family: Literata; font-size: 18px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration: none; float: none; display: inline !important;" class="">with/copy calls the canonical constructor at the end, it's not something that provide a new behavior, but more a syntactic sugar you provide because updating few fields of a record declaring a dozen of components by calling the canonical constructor explicitly involve a lot of boilerplate code that may hide stupid bugs like the values of two components can be swapped because the code called the accessors in the wrong order.</span><br style="caret-color: rgb(0, 0, 0); font-family: Literata; font-size: 18px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration: none;" class=""></div></div></blockquote></div><div class=""><br class=""></div>Well, this is an argument for keyword-based constructors also.<div class="">And reconstructors as well.  (See the connection?  It’s 1.25 things</div><div class="">here not 2 things.)</div><div class=""><br class=""></div><div class="">Setting all of the above aside for now, I have one old and one new</div><div class="">idea about how to smooth out keyword-based calling sequences.</div><div class="">These are offered in the spirit of brainstorming.</div><div class=""><br class=""></div><div class="">The old idea is that, while good old Object… is a fine way to pass</div><div class="">stuff around, we could (not now but later) choose to expand the</div><div class="">set of available varargs calls by adding new carrier types as possible</div><div class="">varargs bundles.  So a key/val/key/val/... sequence could be passed</div><div class="">with keys strongly typed as strings (or enum members, for extra</div><div class="">checking!) and the vals typed as… well Object, still.  The move</div><div class="">needed for such a thing is, I think, simple though somewhat</div><div class="">disruptive.  Sketch of design:</div><div class=""><br class=""></div><div class=""> - have some way for marking a class A as varargs-capable</div><div class=""> - allow a method m to be marked as A-varargs instead of Object[]-varargs (m(…A a)?)</div><div class=""> - transform any call to m(a,b,c…) as m(new A(a,b,…))</div><div class=""> - use the standard rules for constructor resolution in A</div><div class=""> - note that at least one A constructor is probably A-varargs (recursive)</div><div class=""> - this allows A’s constructors to do a L-to-R parse of m’s arguments</div><div class=""> - A(T,U) and A(T,U,…A) give you Map<T,U> key/val/key/val lists</div><div class=""><br class=""></div><div class="">I’m just putting that out there.  We can use Object… for the</div><div class="">foreseeable future.  An enhanced varargs feature would let us do</div><div class="">better type checking, though.  It would also allow the varargs</div><div class="">carrier (A not Object…) to be (drum roll please) an inline type,</div><div class="">getting rid of several kinds of technical debt associated with</div><div class="">array-based varargs.</div><div class=""><br class=""></div><div class="">Second, here’s a new idea:  During the JSR 292 design, we talked</div><div class="">about building BSMs which could somehow capture constant</div><div class="">(or presumed-constant) argument values and fold them into the</div><div class="">target of the call-site.  Remi, your proposed design for record</div><div class=""><strikeout>withers</strikeout> reconstructors could use such</div><div class="">a thing.  We were (IIRC) uncertain how to do this well, although</div><div class="">you may have prototyped something slick like you often do.</div><div class=""><br class=""></div><div class="">This conversation made me revisit the question, and I have a</div><div class="">proposal, a new general-purpose BSM combinator which sets</div><div class="">a “trap” for the first call to a call site, samples the arguments</div><div class="">which are purported to be constant, and then spins a subsidiary</div><div class="">call site which “sees” the constants, and patches the latter call</div><div class="">site into the former.  Various configurations of mutable and</div><div class="">constant call sites are possible and useful.  A new kind of</div><div class="">call site might be desirable, the StableCallSite, which is one</div><div class="">that computes its true target on the first call (not linkage)</div><div class="">and thereafter does not allow target changes.</div><div class=""><br class=""></div><div class="">/** Arrange a call site which samples selected arguments</div><div class="">* on the first call to the call site and calls the indicated bsm</div><div class="">* to hand-craft a sub-call site based on those arguments.</div><div class="">* The bsm is called as CallSite subcs = bsm(L,S,MT,ca…,arg…)</div><div class="">* where the ca values are sampled from the initial dynamic</div><div class="">* list according to caspec. */</div><div class="">StableCallSite bootstrapWithConstantArguments(L,S,MT,caspec,bsm,arg…)</div><div class=""><br class=""></div><div class="">/** Same as bootstrapWithConstantArguments, but the bsm</div><div class="">* is called not only the first time, but every time a new argument</div><div class="">* value is encountered.  Arguments are compared with == not equals.</div><div class="">*/</div><div class=""><div class="">CallSite bootstrapWithSpeculatedArguments(L,S,MT,caspec,bsm,arg…)</div><div class=""><br class=""></div><div class="">Other variations are possible, using other comparators and also</div><div class="">key extractors.  Object::getClass is a great key extractor; this gives</div><div class="">us the pattern of monomorphic inline caches.  For the speculating</div><div class="">version, the existing and new targets could recombined into a</div><div class="">decision tree; that requires an extra hook, perhaps a SwitchingCallSite</div><div class="">or a MH switch combinator.</div><div class=""><br class=""></div></div><div class="">Anyway, I’m brainstorming here, but it seems like we might have</div><div class="">some MH API work to do that would give us leverage on the wither</div><div class="">problem.  It’s probably obvious, but I’ll say it anyway:  The “caspec”</div><div class="">thingy (a String? “0,2,4”?) would point out the places where the key</div><div class="">arguments are placed in the field-polymorphic reconstructor call.</div><div class="">The secondary BSM would take responsibility for building a custom</div><div class="">reconstructor MH that takes the non-key (val) arguments and builds</div><div class="">the requested record.  The primary BSM (bootstrapWithCA) would</div><div class="">recede to the background; it’s just a bit of colorless plumbing, having</div><div class="">no linkage at all to record type translation strategy, other than the</div><div class="">fact that it’s useful.  Maybe there’s a record-specific BSM that wraps</div><div class="">the whole magic trick, but it’s a simple combo on top.</div><div class=""><br class=""></div><div class="">The hard part is building the reconstructor factory.  I think that</div><div class="">should be done in such a way that the record class itself has complete</div><div class="">autonomy over the reconstruction.  Probably the reconstructor</div><div class="">factory should just wire up arguments “foo” where they exist</div><div class="">in the reconstructor call, and pass argument “this.bar” where</div><div class="">they are not mentioned via keys.  This is easy.  It’s clunky too,</div><div class="">but until the JVM gives a real way to say the thing directly,</div><div class="">it will work.  Note that the clunkiness is hidden deep inside</div><div class="">the runtime, and can be swapped out (or optimized) when</div><div class="">a better technique is available, *without changing translation</div><div class="">strategy*.  For records, everything goes through the canonical</div><div class="">constructor, including synthesized reconstructors.  In the</div><div class="">case of *inline* records, the runtime would create a suitable</div><div class="">constructor call, and might (if it could prove it correct) use</div><div class="">bare “withfield” opcodes to make optimization easier.</div><div class=""><br class=""></div><div class="">I think if we expose the API point as MyRecord::with(Object…)</div><div class="">it should be possible to call the thing reflectively, or with</div><div class="">variable keywords, or whatever.  But javac should detect the</div><div class="">common case of non-variable keywords, do some checks,</div><div class="">and replace the call site with an indy, for those cases.  That</div><div class="">way we can have our cake and eat it too.</div><div class=""><br class=""></div><div class="">(There are other things we can do beyond that, by slicing</div><div class="">up the per-variable concerns from the per-instance concerns</div><div class="">in the constructor, leading to better optimizations. The requires</div><div class="">unknown translation strategy hooks.  But this is enough</div><div class="">brainstorming for one email.)</div><div class=""><br class=""></div><div class="">— John</div></body></html>