<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>"Ethan McCue" <ethan@mccue.dev><br><b>To: </b>"Remi Forax" <forax@univ-mlv.fr><br><b>Cc: </b>"John Hendrikx" <john.hendrikx@gmail.com>, "core-libs-dev" <core-libs-dev@openjdk.org><br><b>Sent: </b>Wednesday, August 24, 2022 4:27:01 PM<br><b>Subject: </b>Re: Proposal: Collection mutability marker interfaces<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">> so it's perhaps better to call add() inside a try/catch on UnsupportedOperationException.<div><br>As much as sin is wrong, sketching out what that might look like... forgive the toyness of the example</div></div></blockquote><div><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;"><div dir="ltr"><div><br><br><br>VS<br><br><br><br>final class Ex {<br>    private Ex() {}<br><br>    /*<br>     * Adds the odd numbers from 1 to 10 to the List then makes all the odds even.<br>     *<br>     * Takes ownership of the list, avoids making copies if it doesn't need to<br>     */<br>    static List<Integer> addOdds(List<Integer> l) {<br>        for (int i = 1; i <= 10; i++) {<br>            try {<br>                l.add(i);<br>            } catch (UnsupportedOperationException e) {<br>                l = new ArrayList<>(l);</div></div></blockquote><div><br></div><div>i -= 1;  // restart with an ArrayList<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;"><div dir="ltr"><div><br>            }<br>        }<br><br>        for (int i = 0; i < l.size(); i++) {<br>            if (l.get(i) % 2 == 1) {<br>                try {<br>                    l.set(i, l.get(i) + 1);<br>                } catch (UnsupportedOperationException e) {<br>                    l = new ArrayList<>(l);<br>                }<br>            }<br>        }<br>    }<br>}</div></div></blockquote><div><br></div><div>as Roger said, there is no way in Java to know if the caller has not kept a reference (unlike Rust),</div><div>so having trouble to write this kind of code is more a feature than an issue :)<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>This kind of examples scream the Stream API, which has the correct semantics<br data-mce-bogus="1"></div><div>  IntStream.rangeClosed(1, 10).map(i -> i % 2 == 0? i + 1: i).boxed().toList()<br data-mce-bogus="1"></div><div><br data-mce-bogus="1"></div><div>Rémi<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;"><div dir="ltr"><div><br><br></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Wed, Aug 24, 2022 at 10:03 AM 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="gmail-m_-4085950217033255430zwchr"><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>"Ethan McCue" <<a href="mailto:ethan@mccue.dev" target="_blank">ethan@mccue.dev</a>><br><b>To: </b>"John Hendrikx" <<a href="mailto:john.hendrikx@gmail.com" target="_blank">john.hendrikx@gmail.com</a>><br><b>Cc: </b>"core-libs-dev" <<a href="mailto:core-libs-dev@openjdk.org" target="_blank">core-libs-dev@openjdk.org</a>><br><b>Sent: </b>Wednesday, August 24, 2022 3:38:26 PM<br><b>Subject: </b>Re: Proposal: Collection mutability marker interfaces<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"><div dir="auto">A use case that doesn't cover is adding to a collection.<div dir="auto"><br></div><div dir="auto">Say as part of a method's contract you state that you take ownership of a List. You aren't going to copy even if the list is mutable.</div><div dir="auto"><br></div><div dir="auto">Later on, you may want to add to the list. Add is supported on ArrayList so you don't need to copy and replace your reference, but you would if the list you were given was made with List.of or Arrays.asList</div></div></blockquote><br><div>You can ask if the spliterator considers the collection as immutable or not,<br></div><div>  list.spliterator().hasCharacteristics(Spliterator.IMMUTABLE)<br></div><br><div>sadly, List.of()/Map.of() does not report the spliterator characteristics correctly (the spliterator implementations are inherited from AbstracList/AbstractMap).<br></div><br><div>so it's perhaps better to call add() inside a try/catch on UnsupportedOperationException.<br></div><br><div>Rémi<br></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"><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Wed, Aug 24, 2022, 8:13 AM John Hendrikx <<a href="mailto:john.hendrikx@gmail.com" target="_blank">john.hendrikx@gmail.com</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>
    <p>Would it be an option to not make the receiver responsible for
      the decision whether to make a copy or not?  Instead put this
      burden (using default methods) on the various collections?</p>
    <p>If List/Set/Map had a method like this:</p>
    <p>     List<T> immutableCopy();  // returns a (shallow)
      immutable copy if list is mutable (basically always copies, unless
      proven otherwise)</p>
    <p>Paired with methods on Collections to prevent collections from
      being modified:</p>
    <p>     Collections.immutableList(List<T>)</p>
    <p>This wrapper is similar to `unmodifiableList` except it
      implements `immutableCopy` as `return this`.<br>
    </p>
    <p>Then for the various scenario's, where `x` is an untrusted source
      of List with unknown status:<br>
    </p>
    <p>     // Create a defensive copy; result is a private list that
      cannot be modified:<br>
    </p>
    <p>     List<T> y = x.immutableCopy();   <br>
    </p>
    <p>     // Create a defensive copy for sharing, promising it won't
      ever change:<br>
    </p>
    <p>
           List<T> y = Collections.immutableList(x.immutableCopy());
      <br>
    </p>
    <p>     // Create a defensive copy for mutating:</p>
    <p>     List<T> y = new ArrayList<>(x);  // same as
      always</p>
    <p>     // Create a mutable copy, modify it, then expose as
      immutable:</p>
    <p>     List<T> y = new ArrayList<>(x);  // same as
      always</p>
    <p>     y.add( <some element> );     <br>
    </p>
    <p>     List<T> z = Collections.immutableList(y);</p>
    <p>     y = null;  // we promise `z` won't change again by clearing
      the only path to mutating it!<br>
    </p>
    <p>The advantage would be that this information isn't part of the
      type system where it can easily get lost. The actual
      implementation knows best whether a copy must be made or not.</p>
    <p>Of course, the immutableList wrapper can be used incorrectly and
      the promise here can be broken by keeping a reference to the
      original (mutable) list, but I think that's an acceptable
      trade-off.</p>
    <p>--John</p>
    <p>PS. Chosen names are just for illustration; there is some
      discussion as what "unmodifiable" vs "immutable" means in the
      context of collections that may contain elements that are mutable.
      In this post, immutable refers to shallow immutability .</p>
    <div>On 24/08/2022 03:24, Ethan McCue wrote:<br>
    </div>
    <blockquote>
      
      <div dir="auto">Ah, I'm an idiot.
        <div dir="auto"><br>
        </div>
        <div dir="auto">There is still a proposal here
          somewhere...maybe. right now non jdk lists can't participate
          in the special casing?</div>
      </div>
      <br>
      <div class="gmail_quote">
        <div dir="ltr" class="gmail_attr">On Tue, Aug 23, 2022, 9:00 PM
          Paul Sandoz <<a href="mailto:paul.sandoz@oracle.com" rel="noreferrer" target="_blank">paul.sandoz@oracle.com</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">List.copyOf
          already does what you want.<br>
          <br>
          <a href="https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/List.java#L1068" rel="noreferrer noreferrer noreferrer" target="_blank">https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/List.java#L1068</a><br>
          <a href="https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/ImmutableCollections.java#L168" rel="noreferrer noreferrer noreferrer" target="_blank">https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/ImmutableCollections.java#L168</a><br>
          <br>
          Paul.<br>
          <br>
          > On Aug 23, 2022, at 4:49 PM, Ethan McCue <<a href="mailto:ethan@mccue.dev" rel="noreferrer noreferrer" target="_blank">ethan@mccue.dev</a>> wrote:<br>
          > <br>
          > Hi all,<br>
          > <br>
          > I am running into an issue with the collections framework
          where I have to choose between good semantics for users and
          performance.<br>
          > <br>
          > Specifically I am taking a java.util.List from my users
          and I need to choose to either<br>
          > * Not defensively copy and expose a potential footgun
          when I pass that List to another thread<br>
          > * Defensively copy and make my users pay an unnecessary
          runtime cost.<br>
          > <br>
          > What I would really want, in a nutshell, is for
          List.copyOf to be a no-op when used on lists made with
          List.of().<br>
          > <br>
          > Below the line is a pitch I wrote up on reddit 7 months
          ago for a mechanism I think could accomplish that. My goal is
          to share the idea a bit more widely and to this specific
          audience to get feedback.<br>
          > <br>
          > <a href="https://www.reddit.com/r/java/comments/sf8qrv/comment/hv8or92/?utm_source=share&utm_medium=web2x&context=3" rel="noreferrer noreferrer noreferrer" target="_blank">https://www.reddit.com/r/java/comments/sf8qrv/comment/hv8or92/?utm_source=share&utm_medium=web2x&context=3</a>
          <br>
          > <br>
          > Important also for context is Ron Pressler's comment
          above.<br>
          > --------------<br>
          > <br>
          > What if the collections api added more marker interfaces
          like RandomAccess?<br>
          > <br>
          > It's already a common thing for codebases to make
          explicit null checks at error boundaries because the type
          system can't encode null | List<String>. <br>
          > <br>
          > This feels like a similar problem.<br>
          > If you have a List<T> in the type system then you
          don't know for sure you can call any methods on it until you
          check that its not null. In the same way, there is a set of
          methods that you don't know at the type/interface level if you
          are allowed to call.<br>
          > <br>
          > If the List is actually a __<br>
          > Then you can definitely call<br>
          > And you know other reference holders might call<br>
          > And you can confirm its this case by<br>
          > null<br>
          > no methods<br>
          > no methods<br>
          > list == null<br>
          > List.of(...)<br>
          > get, size<br>
          > get, size<br>
          > ???<br>
          > Collections.unmodifiableList(...)<br>
          > get, size<br>
          > get, size, add, set<br>
          > ???<br>
          > Arrays.asList(...)<br>
          > get, size, set<br>
          > get, size, set<br>
          > ???<br>
          > new ArrayList<>()<br>
          > get, size, add, set<br>
          > get, size, add, set<br>
          > ???<br>
          > While yes, there is no feasible way to encode these
          things in the type system. Its not impossible to encode it at
          runtime though.<br>
          > interface FullyImmutable {<br>
          > // So you know the existence of this implies the absence<br>
          > // of the others<br>
          > default Void cantIntersect() { return null; }<br>
          > }<br>
          > <br>
          > interace MutationCapability {<br>
          > default String cantIntersect() { return ""; }<br>
          > }<br>
          > <br>
          > interface Addable extends MutationCapability {}<br>
          > interface Settable extends MutationCapability {}<br>
          > <br>
          > If the List is actually a __<br>
          > Then you can definitely call<br>
          > And you know other reference holders might call<br>
          > And you can confirm its this case by<br>
          > null<br>
          > no methods<br>
          > no methods<br>
          > list == null<br>
          > List.of(...)<br>
          > get, size<br>
          > get, size<br>
          > instanceof FullyImmutable<br>
          > Collections.unmodifiableList(...)<br>
          > get, size<br>
          > get, size, add, set<br>
          > !(instanceof Addable) && !(instanceof Settable)<br>
          > Arrays.asList(...)<br>
          > get, size, set<br>
          > get, size, set<br>
          > instanceof Settable<br>
          > new ArrayList<>()<br>
          > get, size, add, set<br>
          > get, size, add, set<br>
          > instanceof Settable && instanceof Addable<br>
          > In the same way a RandomAccess check let's
          implementations decide whether they want to try an alternative
          algorithm or crash, some marker "capability" interfaces would
          let users of a collection decide if they want to clone what
          they are given before working on it.<br>
          > <br>
          > <br>
          > --------------<br>
          > <br>
          > So the applicability of this would be that the list
          returned by List.of could implement FullyImmutable, signifying
          that there is no caller which might have a mutable handle on
          the collection. Then List.of could check for this interface
          and skip a copy.<br>
          > <br>
          > <br>
          <br>
        </blockquote>
      </div>
    </blockquote>
  </div>

</blockquote></div></blockquote></div></div></div></blockquote></div><br></blockquote></div></div></body></html>