<html><body><div style="font-family: arial, helvetica, sans-serif; font-size: 12pt; color: #000000"><div>Hello,<br data-mce-bogus="1"></div><div>I would be even more brutal here because I think that the reason *alternative representation* is better serve by factory methods than constructors.<br></div><div><br data-mce-bogus="1"></div><div>The same way, in term of de-constructrion, for *alternative representation*, a named method pattern is better than a deconstructor.<br data-mce-bogus="1"></div><div>So instead of one deconstructor for (A,B) and another one for (X,Y), I think we should stir users ti use two methods asAB() and asXY().<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>For me, i think it's enough, at least for a first preview, to disambiguate only on the deconstructor arity.<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>Note that the deconstructor arity is important because conceptually adding a deconstructor with a supplementary binding is conceptually equivalent to adding a getter to a class,<br data-mce-bogus="1"></div><div>we need that to be able to enhance a class in a backward compatible way.<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>regards,<br data-mce-bogus="1"></div><div>RĂ©mi<br data-mce-bogus="1"></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>"Brian Goetz" <brian.goetz@oracle.com><br><b>To: </b>"amber-spec-experts" <amber-spec-experts@openjdk.java.net><br><b>Sent: </b>Monday, April 1, 2024 6:34:49 PM<br><b>Subject: </b>Deconstructor (and pattern) overload selection<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 class="moz-text-html" lang="x-unicode"> <font size="4"><font face="monospace">The next big pattern matching JEP will be
          about deconstruction patterns.  (Static and instance patterns
          will likely come separately.)  Now that we've got the bikeshed
          painting underway, there are a few other loose ends here, and
          one of them is overload selection.  <br>
          <br>
          We explored taking the existing overload selection algorithm
          and turning it inside out, but after going down that road a
          bit, I think this both unnecessarily much complexity for not
          enough value, and also potentially fraught with nasty corner
          cases.  I think there is a much simpler answer here which is
          entirely good enough.  <br>
          <br>
          First, let's remind ourselves, why do we have constructor
          overloading in the first place?  There are three main reasons:<br>
          <br>
           - Concision.  If a fully-general constructor takes many
          parameters, but not all are essential to the use case, then
          the construction site becomes a site of accidental
          complexity.  Being able to handle common grouping of
          parameters simplifies use sites.<br>
          <br>
           - Flexibility.  Related to the above, not only might the user
          not need to specify a given constructor parameter, but they
          want the flexibility of saying "let the implementation pick
          the best value".  Constructors with fewer parameters reserve
          more flexibility for the implementation.<br>
          <br>
           - Alternative representations.  Some objects may take
          multiple representations as input, such as accepting a Date, a
          LocalDate, or a LocalDateTime.  <br>
          <br>
          The first two cases are generally handled with "telescoping
          constructor nests", where we have:<br>
          <br>
              Foo(A a)<br>
              Foo(A a, B b)<br>
              Foo(A a, B b, C d, D d)<br>
          <br>
          Sometimes the telescopes don't fold perfectly, and becomes
          "trees":<br>
          <br>
              Foo(A a)<br>
              Foo(A a, B b)<br>
              Foo(A a, C c, D d)<br>
        </font></font><font size="4"><font face="monospace"><font size="4"><font face="monospace">    Foo(A a, B b, C d, D d)<br>
              <br>
              Which constructors to include are subjective judgments on
              the part of class authors to find good tradeoffs between
              code size and concision/flexibility.  <br>
              <br>
              We had initially assumed that each constructor overload
              would have a corresponding deconstructor, but further
              experimentation suggests this is not an ideal assumption. 
              <br>
              <br>
              Clue One that it is not a good assumption comes from the
              asymmetry between constructors and deconstructors; if we
              have constructors and deconstructors of shape C(List),
              then it is OK to invoke C's constructor with List or its
              subtypes, but we can invoke C's deconstructor with List or
              its subtypes or its supertypes.  <br>
              <br>
              Clue Two is that applicability for constructors is based
              on method invocation context, but applicability for
              deconstructors is based on cast context, which has
              different rules.  It seems unlikely that we will ever get
              symmetry given this.<br>
              <br>
              The "Flexibility" requirement does not really apply to
              deconstructors; having a deconstructor that accepts
              additional bindings does not constrain anything, not in
              the same way as a constructor taking needlessly specific
              arguments.  Imagine if ArrayList had only constructors
              that take int (for array capacity); this is terrible for
              the constructor, because it forces a resource management
              decision onto users who will not likely make a very good
              decision, and one that is hard to change later, but pretty
              much harmless for deconstructors.  <br>
              <br>
              The "Concision" requirement does not really apply as much
              to deconstructors as constructors; matching with `Foo(var
              a, _, _)` is not nearly as painful as invoking with lots
              of parameters, each of which require an explicit choice by
              the user.  <br>
              <br>
              So the main reason for overloading deconstructors is to
              match representations with the constructor overloads --
              but with a given "representation set", there probably does
              not need to be as many deconstructors as constructors. 
              What we really need is to match the "maximal" constructor
              in a telescoping nest with a corresponding deconstructor,
              or for a tree-shaped set, one for each "maximal"
              representation.  <br>
              <br>
              So for a class with constructors<br>
              <br>
                  Foo()<br>
                  Foo(A a)<br>
                  Foo(A a, B B)<br>
                  Foo(X x)<br>
                  Foo(X x, Y y)<br>
              <br>
              we would want dtors for (A,B) and (X,Y), but don't really
              need the others.  <br>
              <br>
              <br>
              So, let's start fresh on overload selection. 
              Deconstructors have a set of applicability rules based on
              arity first (eventually, varargs, but not yet) and then on
              applicability of type patterns, which is in turn rooted in
              castability.  Because we don't have the compatibility
              problem introduced by autoboxing, we can ignore the
              distinction between phase 1 and 2 of overload selection
              (we will have this problem with varargs later, though.)  <br>
              <br>
              Given this, the main question we have to resolve is to
              what degree -- if any -- we may deem one overload "more
              applicable" than others.  I think there is one rule here
              that is forced: an exact type match (modulo erasure) is
              more applicable than an inexact type match.  So given:<br>
              <br>
                  D(Object o)<br>
                  D(String s)<br>
              <br>
              then<br>
              <br>
                  case D(String s)<br>
              <br>
              should choose the latter.  This allows the client to
              (mostly) steer to a specific overload just by using the
              right types (rather than `var` or a subtype.)  It is not
              clear to me whether we need anything more here; in the
              event of ambiguity, a client can pick the right overload
              with the right type patterns.  (Nested patterns may need
              to be manually unrolled to subsequent clauses in some
              cases.)  <br>
              <br>
              So basically (on a per-binding basis): an exact match is
              more applicable than an inexact match, and ... that's it. 
              Users can steer towards a particular overload by selecting
              exact matches on enough bindings.  Libraries can provide
              their own "joins" if they want to disambiguate problematic
              overloads like:<br>
              <br>
                  D(Object o, String s)<br>
                  D(String s, Object o)<br>
              <br>
              <br>
              <br>
              <br>
            </font></font></font></font> </div><br></blockquote></div></div></body></html>