On 24/08/2022 15:38, Ethan McCue wrote:
A use case that doesn't cover is adding to a collection.
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.

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

I don't think this is a common enough use case that should be catered for.  It might be better handled with concurrent lists instead.

The most common use case by far is wanting to make sure a collection you've received is not going to be modified while you are working with it.  I don't think another proposal which does cover the most common cases should be dismissed out of hand because it doesn't support a rather rare use case.

--John



On Wed, Aug 24, 2022, 8:13 AM John Hendrikx <john.hendr...@gmail.com> wrote:

    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?

    If List/Set/Map had a method like this:

         List<T> immutableCopy();  // returns a (shallow) immutable
    copy if list is mutable (basically always copies, unless proven
    otherwise)

    Paired with methods on Collections to prevent collections from
    being modified:

         Collections.immutableList(List<T>)

    This wrapper is similar to `unmodifiableList` except it implements
    `immutableCopy` as `return this`.

    Then for the various scenario's, where `x` is an untrusted source
    of List with unknown status:

         // Create a defensive copy; result is a private list that
    cannot be modified:

         List<T> y = x.immutableCopy();

         // Create a defensive copy for sharing, promising it won't
    ever change:

         List<T> y = Collections.immutableList(x.immutableCopy());

         // Create a defensive copy for mutating:

         List<T> y = new ArrayList<>(x);  // same as always

         // Create a mutable copy, modify it, then expose as immutable:

         List<T> y = new ArrayList<>(x);  // same as always

         y.add( <some element> );

         List<T> z = Collections.immutableList(y);

         y = null;  // we promise `z` won't change again by clearing
    the only path to mutating it!

    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.

    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.

    --John

    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 .

    On 24/08/2022 03:24, Ethan McCue wrote:
    Ah, I'm an idiot.

    There is still a proposal here somewhere...maybe. right now non
    jdk lists can't participate in the special casing?

    On Tue, Aug 23, 2022, 9:00 PM Paul Sandoz
    <paul.san...@oracle.com> wrote:

        List.copyOf already does what you want.

        
https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/List.java#L1068
        
https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/ImmutableCollections.java#L168

        Paul.

        > On Aug 23, 2022, at 4:49 PM, Ethan McCue <et...@mccue.dev>
        wrote:
        >
        > Hi all,
        >
        > I am running into an issue with the collections framework
        where I have to choose between good semantics for users and
        performance.
        >
        > Specifically I am taking a java.util.List from my users and
        I need to choose to either
        > * Not defensively copy and expose a potential footgun when
        I pass that List to another thread
        > * Defensively copy and make my users pay an unnecessary
        runtime cost.
        >
        > 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().
        >
        > 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.
        >
        >
        
https://www.reddit.com/r/java/comments/sf8qrv/comment/hv8or92/?utm_source=share&utm_medium=web2x&context=3
        
<https://www.reddit.com/r/java/comments/sf8qrv/comment/hv8or92/?utm_source=share&utm_medium=web2x&context=3>

        >
        > Important also for context is Ron Pressler's comment above.
        > --------------
        >
        > What if the collections api added more marker interfaces
        like RandomAccess?
        >
        > 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>.
        >
        > This feels like a similar problem.
        > 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.
        >
        > If the List is actually a __
        > Then you can definitely call
        > And you know other reference holders might call
        > And you can confirm its this case by
        > null
        > no methods
        > no methods
        > list == null
        > List.of(...)
        > get, size
        > get, size
        > ???
        > Collections.unmodifiableList(...)
        > get, size
        > get, size, add, set
        > ???
        > Arrays.asList(...)
        > get, size, set
        > get, size, set
        > ???
        > new ArrayList<>()
        > get, size, add, set
        > get, size, add, set
        > ???
        > While yes, there is no feasible way to encode these things
        in the type system. Its not impossible to encode it at
        runtime though.
        > interface FullyImmutable {
        > // So you know the existence of this implies the absence
        > // of the others
        > default Void cantIntersect() { return null; }
        > }
        >
        > interace MutationCapability {
        > default String cantIntersect() { return ""; }
        > }
        >
        > interface Addable extends MutationCapability {}
        > interface Settable extends MutationCapability {}
        >
        > If the List is actually a __
        > Then you can definitely call
        > And you know other reference holders might call
        > And you can confirm its this case by
        > null
        > no methods
        > no methods
        > list == null
        > List.of(...)
        > get, size
        > get, size
        > instanceof FullyImmutable
        > Collections.unmodifiableList(...)
        > get, size
        > get, size, add, set
        > !(instanceof Addable) && !(instanceof Settable)
        > Arrays.asList(...)
        > get, size, set
        > get, size, set
        > instanceof Settable
        > new ArrayList<>()
        > get, size, add, set
        > get, size, add, set
        > instanceof Settable && instanceof Addable
        > 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.
        >
        >
        > --------------
        >
        > 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.
        >
        >

Reply via email to