All trivial collection operations scream stream api and all stream api operations imply a full copy or at least a full scan.
> so having trouble to write this kind of code is more a feature than an issue :) I love all Java code equally On Wed, Aug 24, 2022 at 11:21 AM <fo...@univ-mlv.fr> wrote: > > > ------------------------------ > > *From: *"Ethan McCue" <et...@mccue.dev> > *To: *"Remi Forax" <fo...@univ-mlv.fr> > *Cc: *"John Hendrikx" <john.hendr...@gmail.com>, "core-libs-dev" < > core-libs-dev@openjdk.org> > *Sent: *Wednesday, August 24, 2022 4:27:01 PM > *Subject: *Re: Proposal: Collection mutability marker interfaces > > > so it's perhaps better to call add() inside a try/catch on > UnsupportedOperationException. > > As much as sin is wrong, sketching out what that might look like... > forgive the toyness of the example > > > > > > > VS > > > > final class Ex { > private Ex() {} > > /* > * Adds the odd numbers from 1 to 10 to the List then makes all the > odds even. > * > * Takes ownership of the list, avoids making copies if it doesn't > need to > */ > static List<Integer> addOdds(List<Integer> l) { > for (int i = 1; i <= 10; i++) { > try { > l.add(i); > } catch (UnsupportedOperationException e) { > l = new ArrayList<>(l); > > > i -= 1; // restart with an ArrayList > > > } > } > > for (int i = 0; i < l.size(); i++) { > if (l.get(i) % 2 == 1) { > try { > l.set(i, l.get(i) + 1); > } catch (UnsupportedOperationException e) { > l = new ArrayList<>(l); > } > } > } > } > } > > > as Roger said, there is no way in Java to know if the caller has not kept > a reference (unlike Rust), > so having trouble to write this kind of code is more a feature than an > issue :) > > This kind of examples scream the Stream API, which has the correct > semantics > IntStream.rangeClosed(1, 10).map(i -> i % 2 == 0? i + 1: > i).boxed().toList() > > Rémi > > > > > On Wed, Aug 24, 2022 at 10:03 AM Remi Forax <fo...@univ-mlv.fr> wrote: > >> >> >> ------------------------------ >> >> *From: *"Ethan McCue" <et...@mccue.dev> >> *To: *"John Hendrikx" <john.hendr...@gmail.com> >> *Cc: *"core-libs-dev" <core-libs-dev@openjdk.org> >> *Sent: *Wednesday, August 24, 2022 3:38:26 PM >> *Subject: *Re: Proposal: Collection mutability marker interfaces >> >> 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 >> >> >> You can ask if the spliterator considers the collection as immutable or >> not, >> list.spliterator().hasCharacteristics(Spliterator.IMMUTABLE) >> >> sadly, List.of()/Map.of() does not report the spliterator characteristics >> correctly (the spliterator implementations are inherited from >> AbstracList/AbstractMap). >> >> so it's perhaps better to call add() inside a try/catch on >> UnsupportedOperationException. >> >> Rémi >> >> >> 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 >>>> > >>>> > 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. >>>> > >>>> > >>>> >>>> >