<html><body><div style="font-family: arial, helvetica, sans-serif; font-size: 12pt; color: #000000"><div><br></div><div><br></div><hr id="zwchr" data-marker="__DIVIDER__"><div data-marker="__HEADERS__"><blockquote style="border-left:2px solid #1010FF;margin-left:5px;padding-left:5px;color:#000;font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt;"><b>From: </b>"David Alayachew" <davidalayachew@gmail.com><br><b>To: </b>"Remi Forax" <forax@univ-mlv.fr><br><b>Cc: </b>"Brian Goetz" <brian.goetz@oracle.com>, "amber-dev" <amber-dev@openjdk.org><br><b>Sent: </b>Thursday, September 21, 2023 8:40:05 PM<br><b>Subject: </b>Re: Question about records and overriding their "magic methods"<br></blockquote></div><div data-marker="__QUOTED_TEXT__"><blockquote style="border-left:2px solid #1010FF;margin-left:5px;padding-left:5px;color:#000;font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt;"><div dir="ltr"><div class="gmail_default" style="font-family:monospace">Hello Rémi,<br><br>Thank you for your response!<br><br>> If you really really want to do it, you can use the<br>> bootstrap method in java.lang.runtime.ObjectsMethods but<br>> it's ugly.<br><br>That's actually way better than I thought it would be. It's surprisingly direct too.<br><br>> We also do not want to close the door to supporting<br>> super.x() if at some point in the future if the VM spec<br>> is changed to introduce a way to generate bridge methods<br>> at runtime.<br>> <br>> The record methods toString/equals/hashCode will be<br>> declare as abstract bridge method in that case.<br><br>I brushed up on bridge methods for this response - <a href="https://docs.oracle.com/javase/tutorial/java/generics/bridgeMethods.html" target="_blank">https://docs.oracle.com/javase/tutorial/java/generics/bridgeMethods.html</a><br><br>I get the concept, but I am curious what having that at runtime would give us that we don't already get at compile time. I don't quite see it.</div></div></blockquote><div><br></div><div>The idea is to use a method handle to implement those dynamically. Anyway, thinking a little bit more, there is perhaps a simpler solution.<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>Let state the issue, toString/equals/hashCode are abstract in Record, so you can not call super.toString().<br data-mce-bogus="1"></div><div>If we want to support that, it means that either as Brian said we invent a way to ask for a default implementation provided by the compiler with a new syntax or we make toString/equals/hashCode non-abstract and we provide a code for them.<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>As Brian said, the former is too adhoc and the latter run into the fact that while we can provide generic implementations inside java.lang.Record, those implementations will be slow because inside java.lang.Record we need to dynamically ask what is the actual record and then call a generic code with that record class.</div><div><br data-mce-bogus="1"></div><div>Another possible solution is to implement Record.equals/toString/hashCode to throw an exception saying they are magic methods and to re-route all access to use an invokedynamic instead.<br data-mce-bogus="1"></div><div>So</div><div>- the compiler should trap the calls to super.toString()/equals()/hashCode() and generate an invokedynamic instead.<br data-mce-bogus="1"></div><div>- the VM should trap reflection calls and method handle creations to use a method handle produced by ObjectMethods.bootstrap() instead.<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>For me, that's a lot of engineering for a use case that will be used once in a while.</div><div><br data-mce-bogus="1"></div><blockquote style="border-left:2px solid #1010FF;margin-left:5px;padding-left:5px;color:#000;font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt;"><div dir="ltr"><div class="gmail_default" style="font-family:monospace"><br><br>Thank you for your time and help!<br>David Alayachew</div></div></blockquote><div><br></div><div>Regards,<br data-mce-bogus="1"></div><div>Rémi<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>PS: we may be able in the future when specialized generics will be landed to re-visit that issue because we will get a way to have static method code specialized by a constant record class but this will still require a lot of engineering, i am afraid.<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><blockquote style="border-left:2px solid #1010FF;margin-left:5px;padding-left:5px;color:#000;font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt;"><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Thu, Sep 21, 2023 at 1:49 PM Remi Forax <<a href="mailto:forax@univ-mlv.fr" target="_blank">forax@univ-mlv.fr</a>> wrote:<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><div style="font-family:arial,helvetica,sans-serif;font-size:12pt;color:rgb(0,0,0)"><br><br><hr id="m_-1013115352825319821zwchr"><div><blockquote style="border-left:2px solid rgb(16,16,255);margin-left:5px;padding-left:5px;color:rgb(0,0,0);font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt"><b>From: </b>"Brian Goetz" <<a href="mailto:brian.goetz@oracle.com" target="_blank">brian.goetz@oracle.com</a>><br><b>To: </b>"David Alayachew" <<a href="mailto:davidalayachew@gmail.com" target="_blank">davidalayachew@gmail.com</a>>, "amber-dev" <<a href="mailto:amber-dev@openjdk.org" target="_blank">amber-dev@openjdk.org</a>><br><b>Sent: </b>Thursday, September 21, 2023 7:18:13 PM<br><b>Subject: </b>Re: Question about records and overriding their "magic methods"<br></blockquote></div><div><blockquote style="border-left:2px solid rgb(16,16,255);margin-left:5px;padding-left:5px;color:rgb(0,0,0);font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt"><span style="font-size:large"><span style="font-family:monospace">We explored this topic somewhat when designing the feature.  <br><br>When you override a method x, you can still delegate (via super.x()) to the thing you override.  We considered doing the same here (delegating to default.x()), but concluded that this failed the power-to-weight-ratio test, because usually you do not want to _refine_ an equals/toString/hashCode calculation, but instead broaden it (such as comparing arrays by contents rather than by ==.)  <br><br>If you pull on this "string" a bit, the API that you would want here is complex and enormous, and has as many tuning knobs as a Lombok.  So refactoring records that customize Object methods becomes a higher-responsibility activity.  Leave "future you" some comments for what you were thinking today.</span></span></blockquote><br><br><div>If you really really want to do it, you can use the bootstrap method in java.lang.runtime.ObjectsMethods but it's ugly.<br></div><br><div><div style="background-color:rgb(255,255,255);color:rgb(8,8,8)"><div style="background-color:rgb(255,255,255);color:rgb(8,8,8)"><div style="background-color:rgb(255,255,255);color:rgb(8,8,8)"><pre style="font-family:"JetBrains Mono",monospace;font-size:9.8pt"><span style="color:rgb(0,51,179)">public class </span><span style="color:rgb(0,0,0)">CallingRecordToStringDemo </span>{<br><br>  <span style="color:rgb(0,51,179)">record </span><span style="color:rgb(0,0,0)">Foo</span>(<span style="color:rgb(0,0,0)">String </span>blah, <span style="color:rgb(0,51,179)">int </span>value) {<br>    <span style="color:rgb(0,51,179)">private static final </span><span style="color:rgb(0,0,0)">MethodHandle </span><span style="color:rgb(135,16,148);font-style:italic">TO_STRING</span>;<br>    <span style="color:rgb(0,51,179)">static </span>{<br>      <span style="color:rgb(0,51,179)">var </span><span style="color:rgb(0,0,0)">lookup </span>= <span style="color:rgb(0,0,0)">MethodHandles</span>.<span style="font-style:italic">lookup</span>();<br>      <span style="color:rgb(0,51,179)">var </span><span style="color:rgb(0,0,0)">components </span>= <span style="color:rgb(0,0,0)">Foo</span>.<span style="color:rgb(0,51,179)">class</span>.getRecordComponents();<br>      <span style="color:rgb(0,0,0)">CallSite callsite</span>;<br>      <span style="color:rgb(0,51,179)">try </span>{<br>        <span style="color:rgb(0,0,0)">callsite </span>= (<span style="color:rgb(0,0,0)">CallSite</span>) <span style="color:rgb(0,0,0)">ObjectMethods</span>.<span style="font-style:italic">bootstrap</span>(<span style="color:rgb(0,0,0)">lookup</span>,<br>            <span style="color:rgb(6,125,23)">"toString"</span>,<br>            <span style="color:rgb(0,0,0)">MethodType</span>.<span style="font-style:italic">methodType</span>(<span style="color:rgb(0,0,0)">String</span>.<span style="color:rgb(0,51,179)">class</span>, <span style="color:rgb(0,0,0)">Foo</span>.<span style="color:rgb(0,51,179)">class</span>),<br>            <span style="color:rgb(0,0,0)">Foo</span>.<span style="color:rgb(0,51,179)">class</span>,<br>            <span style="color:rgb(0,0,0)">Arrays</span>.<span style="font-style:italic">stream</span>(<span style="color:rgb(0,0,0)">components</span>).map(<span style="color:rgb(0,0,0)">RecordComponent</span>::getName).collect(<span style="color:rgb(0,0,0)">Collectors</span>.<span style="font-style:italic">joining</span>(<span style="color:rgb(6,125,23)">";"</span>)),<br>            <span style="color:rgb(0,0,0)">Arrays</span>.<span style="font-style:italic">stream</span>(<span style="color:rgb(0,0,0)">components</span>).map(c -> {<br>              <span style="color:rgb(0,51,179)">try </span>{<br>                <span style="color:rgb(0,51,179)">return </span><span style="color:rgb(133,22,145)">lookup</span>.unreflect(c.getAccessor());<br>              } <span style="color:rgb(0,51,179)">catch </span>(<span style="color:rgb(0,0,0)">IllegalAccessException </span>e) {<br>                <span style="color:rgb(0,51,179)">throw new </span>AssertionError(e);<br>              }<br>            }).toArray(<span style="color:rgb(0,0,0)">MethodHandle</span>[]::<span style="color:rgb(0,51,179)">new</span>));<br>      } <span style="color:rgb(0,51,179)">catch </span>(<span style="color:rgb(0,0,0)">Throwable </span>e) {<br>        <span style="color:rgb(0,51,179)">throw new </span>AssertionError(e);<br>      }<br>      <span style="color:rgb(135,16,148);font-style:italic">TO_STRING </span>= <span style="color:rgb(0,0,0)">callsite</span>.dynamicInvoker();<br>    }<br><br>    <span style="color:rgb(158,136,13)">@Override<br></span><span style="color:rgb(0,51,179)">public </span><span style="color:rgb(0,0,0)">String </span><span style="color:rgb(0,98,122)">toString</span>() {<br>      <span style="color:rgb(0,51,179)">try </span>{<br>        <span style="color:rgb(0,51,179)">return </span>(<span style="color:rgb(0,0,0)">String</span>) <span style="color:rgb(135,16,148);font-style:italic">TO_STRING</span>.invokeExact(<span style="color:rgb(0,51,179)">this</span>);<br>      } <span style="color:rgb(0,51,179)">catch </span>(<span style="color:rgb(0,0,0)">Error </span>e) {<br>        <span style="color:rgb(0,51,179)">throw </span>e;<br>      } <span style="color:rgb(0,51,179)">catch </span>(<span style="color:rgb(0,0,0)">Throwable </span>e) {<br>        <span style="color:rgb(0,51,179)">throw new </span>AssertionError(e);<br>      }<br>    }<br>  }<br><br>  <span style="color:rgb(0,51,179)">public static void </span><span style="color:rgb(0,98,122)">main</span>(<span style="color:rgb(0,0,0)">String</span>[] args) {<br>    <span style="color:rgb(0,0,0)">System</span>.<span style="color:rgb(135,16,148);font-style:italic">out</span>.println(<span style="color:rgb(0,51,179)">new </span>Foo(<span style="color:rgb(6,125,23)">"blah"</span>, <span style="color:rgb(23,80,235)">42</span>));<br>  }<br>}</pre></div></div></div></div><br><div>We also do not want to close the door to supporting super.x() if at some point in the future if the VM spec is changed to introduce a way to generate bridge methods at runtime.</div><div>The record methods toString/equals/hashCode will be declare as abstract bridge method in that case.<br></div><br><div>regards,</div><div>Rémi</div><br><blockquote style="border-left:2px solid rgb(16,16,255);margin-left:5px;padding-left:5px;color:rgb(0,0,0);font-weight:normal;font-style:normal;text-decoration:none;font-family:Helvetica,Arial,sans-serif;font-size:12pt"><span style="font-size:large"><span style="font-family:monospace"><br><br></span></span><br><div>On 9/21/2023 1:00 PM, David Alayachew wrote:<br></div><blockquote><div dir="ltr"><div class="gmail_default" style="font-family:monospace">Hello Amber Dev Team,</div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace">Let's say I have a record like the following.</div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace">record ComplexRecord(int blah /* and many more components */) {}</div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace">Next, let's say that I want to override my toString, to include some derived information.</div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace">Obviously, putting the derived info into the toString is easy, but how do I go about INCLUDING it with my original implementation of toString that was magically created for me by Java?</div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace">If I try to fully recreate my toString, I run the risk of it becoming out-of-date upon refactor. Best I can come up with is nesting another record with the toString overloaded. Also not ideal.</div><div class="gmail_default" style="font-family:monospace"><br></div><div class="gmail_default" style="font-family:monospace">Thank you for your time!</div><div class="gmail_default" style="font-family:monospace">David Alayachew<br></div></div></blockquote></blockquote></div></div></div></blockquote></div><br></blockquote></div></div></body></html>