Re: Proposal: Replace foreach loop with Iterable.forEach in String.join(CharSequence, Iterable)

2021-08-17 Thread Donald Raab
Hi Claes,

> so the win comes from forEach on the synchronized collection implicitly
> being synchronized atomically, whereas foreach will synchronize on
> each iterator operation and thus allow racy modification of elements
> in between iteration, right?

Yes, this win will be applied to any type that does something special in 
forEach. For instance we have MultiReader collections in Eclipse Collections, 
which take a read lock on forEach, and throws UnsupportedOperationException on 
iterator. This change would make String.join safe to use for these as well, 
without requiring an explicit lock in client code.

> 
> Seems reasonable to consider, though it'd have to percolate through to most 
> production deployments before you could use it - and even then
> it seems somewhat fragile. And isn't this just one of many utility
> methods that would have to be fixed to avoid issues with hidden
> iterators completely?
> 

Yes, this is one small step. Synchronized collections wouldn’t become less 
tricky to use, but there would be one less hidden iterator to worry about.

> String.join was recently changed to this by JDK-8265237[1]:
> 
>for (CharSequence cs: elements) {
>if (size >= elems.length) {
>elems = Arrays.copyOf(elems, elems.length << 1);
>}
>elems[size++] = String.valueOf(cs);
>}
> 

This looks like the code for the overloaded String.join which takes an array. I 
was referring to only the version that takes Iterable.

> Of course the body in the new code could be extracted to the same
> effect. The drawback in either implementation is that it would by
> necessity be a capturing lambda, so might add a small allocation
> overhead. I'm biased towards avoiding performance overheads, but maybe
> someone else can make a call on whether this is a worthwhile thing to
> attempt.
> 

There is a trade-off here. You remove the need to create an iterator for all 
types (due to foreach loop), and replace with forEach with a method reference, 
which IIRC might be slightly better than having a lambda. The underlying 
collection (let’s say ArrayList) will be called which will also not create an 
iterator but iterate directly over the ArrayList internal array.

Thanks,
Don

> Thanks!
> 
> /Claes
> 
> [1] https://bugs.openjdk.java.net/browse/JDK-8265237
> 
> On 2021-08-17 06:55, Donald Raab wrote:
>> The following code:
>> for (CharSequence cs: elements) {
>> joiner.add(cs);
>> }
>> Can be replaced with:
>> elements.forEach(joiner::add);
>> This would have the minor benefit of making it safe to use String.join with 
>> synchronized collections without requiring a client side lock. There are 
>> likely other opportunities like this in the JDK.
>> Also, with the addition of forEach on Iterable, and Predicate in Java 8, the 
>> following design FAQ is outdated.
>> https://docs.oracle.com/en/java/javase/16/docs/api/java.base/java/util/doc-files/coll-designfaq.html#a6
>>  
>> <https://docs.oracle.com/en/java/javase/16/docs/api/java.base/java/util/doc-files/coll-designfaq.html#a6>
>> Thanks,
>> Don



Proposal: Replace foreach loop with Iterable.forEach in String.join(CharSequence, Iterable)

2021-08-16 Thread Donald Raab
The following code:

for (CharSequence cs: elements) {
joiner.add(cs);
}

Can be replaced with:

elements.forEach(joiner::add);

This would have the minor benefit of making it safe to use String.join with 
synchronized collections without requiring a client side lock. There are likely 
other opportunities like this in the JDK. 

Also, with the addition of forEach on Iterable, and Predicate in Java 8, the 
following design FAQ is outdated.

https://docs.oracle.com/en/java/javase/16/docs/api/java.base/java/util/doc-files/coll-designfaq.html#a6
 

 

Thanks,
Don

Re: New convenience methods on Stream

2021-06-13 Thread Donald Raab
ad
> idea. Imagine if the custom collection has a fixed element type:
> 
> class MyStringCollection implements Collection {
>  public MyStringCollection(String[] array) {...}
> }
> 
> It looks like stream.to(MyStringCollection::new) should work but in
> fact, it will throw a ClassCastException
> 
> 3. into() sounds more interesting as it's indeed useful to dump the
> stream into an existing collection. It's mostly useful if the
> collection is non-empty, as you can append into single collection from
> existing sources. Essentially, into(c) == forEach(c::add), but it's
> also possible to add optimizations for specific collections (like `if
> (isSizedStream() && c instanceof ArrayList al) {
> al.ensureCapacity(...); }`), so it could be faster.
> 
> With best regards,
> Tagir Valeev
> 
> [1] https://bugs.openjdk.java.net/browse/JDK-8140283
> 
> On Thu, Apr 29, 2021 at 5:58 AM Donald Raab  wrote:
>> 
>> I looked through a few libraries and found some methods where the option #2 
>> proposal for Steam might be useful. If the JDK had constructors for 
>> ArrayList, HashSet and other collection types that take arrays this method 
>> would work there as well.
>> 
>>> default > R to(Function function)
>>> {
>>>   return function.apply((T[]) this.toArray());
>>> }
>> 
>> 
>> // JDK
>> Set set = stream.to(Set::of);
>> List list = stream.to(List::of);
>> List arraysAsList = stream.to(Arrays::asList);
>> 
>> // Guava
>> ArrayList arrayList = stream.to(Lists::newArrayList);
>> HashSet hashSet = stream.to(Sets::newHashSet);
>> Multiset multiset = stream.to(ImmutableMultiset::copyOf);
>> List guavaList = stream.to(ImmutableList::copyOf);
>> Set guavaSet = stream.to(ImmutableSet::copyOf);
>> 
>> // Apache Commons Collections
>> FluentIterable fluentIterable = stream.to(FluentIterable::of);
>> 
>> // Eclipse Collections
>> MutableList adapter = stream.to(ArrayAdapter::adapt);
>> 
>> MutableList mutableList = stream.to(Lists.mutable::with);
>> MutableSet mutableSet = stream.to(Sets.mutable::with);
>> MutableBag mutableBag = stream.to(Bags.mutable::with);
>> 
>> // Eclipse Collections - ListIterable, SetIterable and Bag all extend 
>> Iterable, not Collection
>> ListIterable listIterable = stream.to(Lists.mutable::with);
>> SetIterable setIterable = stream.to(Sets.mutable::with);
>> Bag bag = stream.to(Bags.mutable::with);
>> 
>> // Eclipse Collections - Immutable Collections do not extend Collection
>> ImmutableList immutableList = stream.to(Lists.immutable::with);
>> ImmutableSet immutableSet = stream.to(Sets.immutable::with);
>> ImmutableBag immutableBag = stream.to(Bags.immutable::with);
>> 
>> // Eclipse Collections - Stack does not extend Collection
>> StackIterable stackIterable = stream.to(Stacks.mutable::with);
>> MutableStack mutableStack = stream.to(Stacks.mutable::with);
>> ImmutableStack immutableStack = stream.to(Stacks.immutable::with);
>> 
>> // Eclipse Collections - Mutable Map and MutableBiMap are both Iterable 
>> so they are valid returns
>> MutableMap map =
>>stream.to(array -> ArrayAdapter.adapt(array)
>>.toMap(String::toLowerCase, String::toUpperCase));
>> 
>> MutableBiMap biMap =
>>stream.to(array -> ArrayAdapter.adapt(array)
>>.toBiMap(String::toLowerCase, String::toUpperCase));
>> 
>> Thanks,
>> Don
>> 
>>> On Apr 27, 2021, at 1:35 AM, Donald Raab  wrote:
>>> 
>>> I realized after sending that option 2 can be made more abstract:
>>> 
>>> default > R to(Function function)
>>> {
>>>   return function.apply((T[]) this.toArray());
>>> }
>>> 
>>>> 
>>>> 2. Pass the result of toArray directly into a function that can then 
>>>> return a Collection. This should work with Set.of, List.of and any 3rd 
>>>> party collections which take arrays.
>>>> 
>>>> default > R to(Function function)
>>>> {
>>>>  return function.apply((T[]) this.toArray());
>>>> }
>>>> 
>>>> Usage Examples:
>>>> 
>>>> Set set = stream.to(Set::of);
>>>> List list = stream.to(List::of);
>>>> List arrayList = stream.to(Arrays::asList);
>>>> 
>>> 
>> 



Re: Collection::getAny discussion

2021-04-30 Thread Donald Raab
To clarify, RichIterable is not a subclass of Collection. As we discovered in 
JDK 15, a problem exists when we add default methods to interfaces that might 
get “mixed” with other interfaces that already have those methods. There are a 
few potential issues with adding zero argument default methods to common 
interfaces. The two easiest to reason about that we have experience with are:

1. Signatures don’t match (e.g. getAny() returns Optional) - very bad - lots of 
pain caused - forces backwards incompatible change to library APIs - this 
happened when default sort() was added to List and returned void
2. Signatures match, but no concrete implementations when mixing competing 
default implementations - work for library developers - forces clients to 
upgrade to new version of library to use new version of JDK (e.g. EC 10.4 
upgrade to work with JDK 15 for CharSequence.isEmpty())

https://stuartmarks.wordpress.com/2020/09/22/incompatibilities-with-jdk-15-charsequence-isempty/
 

I think adding getAny() to Collection makes sense, that’s why we have defined 
the method on RichIterable. For Eclipse Collections, option #2 is the only 
option that works (getAny() returns E). It will cause us OSS library 
maintainers a bunch of work and a release upgrade. Knowing the direction and 
testing with early access versions (as we do) prior to our next release will 
help so we can start coding out any necessary concrete implementations wherever 
they don’t exist in the hierarchy.

The default getAny() implementation on RichIterable is different than just 
calling iterator.next() though. The getAny() method is currently defined as 
calling getFirst() which will return null in the case an iterable is empty. 

So this creates a new kind of potential issue we haven’t experienced where we 
could wind up with two getAny() methods with the same signature but with 
different specifications and results on empty. This will be potentially 
confusing for Eclipse Collections users as they could get different behavior 
from any JDK collections for getAny(), or libraries they use could get 
different behavior for Eclipse Collections types that extend JDK types. Do we 
change the Eclipse Collections specification to match the new JDK 
specification? Will this break anyone that currently uses it by causing new 
unexpected Runtime exceptions? I honestly don’t know.
 
For additional reference, there is also a getOnly() method on RichIterable 
which behaves differently than getAny(). 

https://www.eclipse.org/collections/javadoc/10.4.0/org/eclipse/collections/api/RichIterable.html#getOnly()

There are also getFirst() and getLast() methods on RichIterable which were 
deprecated in 6.0 but will never be removed. They were added to OrderedIterable 
where they make more sense.

 

> On Apr 30, 2021, at 4:14 PM, Brian Goetz  wrote:
> 
> While I agree that we should be careful, let's not paint ourselves into an 
> either/or strawman.  The choice is not "never add anything to Collection" vs 
> "let's dump every silly idea that comes to anyone's mind into Collection"; it 
> is, as always, going to involve tradeoffs between stability and evolution.  
> 
> We cannot constrain ourselves so hard that we cannot evolve the core 
> libraries because it might collide with someone else's subclass.  That's not 
> reasonable, nor is that good for Java.
> 
> On the other hand, we must continue to exercise care in many dimensions when 
> adding to libraries that are widely used and implemented -- which we already 
> do (so much so, in fact, that people are often frustrated by our seeming 
> conservatism.)  
> 
> 
> 
> 
> 
> 
> 
> On 4/30/2021 4:02 PM, Donald Raab wrote:
>> There is a default method getAny defined on the RichIterable interface in 
>> Eclipse Collections. Adding a getAny with the same signature to Collection 
>> is bound to cause a break similar to CharSequence.isEmpty did with JDK 15 
>> but possibly more extensive since RichIterable is the parent interface for 
>> all collection types in Eclipse Collections. Adding it with a different 
>> signature (returns Optional) could cause extensive damage.
>> 
>> https://www.eclipse.org/collections/javadoc/10.4.0/org/eclipse/collections/api/RichIterable.html#getAny()
>>  
>> <https://www.eclipse.org/collections/javadoc/10.4.0/org/eclipse/collections/api/RichIterable.html#getAny()>
>>  
>> 
>> I highly recommend we stop looking to add new zero-argument default methods 
>> to 20+ year Collection interfaces and hope that we don’t break anything. 
>> There seems to be desire to breathe life into the old Collection interfaces. 
>> IMO, we should just start planning and focusing on a Collections 2.0 design.
>> 
>> 
>>> On Apr 30, 2021, at 2:49 PM, Stuart Marks  
>>>

Re: Collection::getAny discussion

2021-04-30 Thread Donald Raab
There is a default method getAny defined on the RichIterable interface in 
Eclipse Collections. Adding a getAny with the same signature to Collection is 
bound to cause a break similar to CharSequence.isEmpty did with JDK 15 but 
possibly more extensive since RichIterable is the parent interface for all 
collection types in Eclipse Collections. Adding it with a different signature 
(returns Optional) could cause extensive damage.

https://www.eclipse.org/collections/javadoc/10.4.0/org/eclipse/collections/api/RichIterable.html#getAny()
 

I highly recommend we stop looking to add new zero-argument default methods to 
20+ year Collection interfaces and hope that we don’t break anything. There 
seems to be desire to breathe life into the old Collection interfaces. IMO, we 
should just start planning and focusing on a Collections 2.0 design.


> On Apr 30, 2021, at 2:49 PM, Stuart Marks  wrote:
> 
> Hi Henri,
> 
> I've changed the subject of this thread because I think it's out of scope of 
> the ReversibleCollection proposal. I don't mean to say that we can't talk 
> about it, but I would like it to be decoupled from ReversibleCollection. I'm 
> somewhat arbitrarily calling it "Collection::getAny" because something 
> similar to that was mentioned by both Remi and Peter elsewhere in this 
> thread. There is also a bunch of history in the bug database that contains 
> related ideas.
> 
> Before we dive in, I want to explain why this is separate from 
> ReversibleCollection. Most of the ideas, including yours, involve an 
> implementation that does something like `iterator().next()`, in other words, 
> getting the "first" element from an Iterator. Hey, there's getFirst() in 
> ReversibleCollection, let's use that! No. The "first" element of an iterator 
> is in general an arbitrary element, which is different from the "first" 
> element in the structural ordering of elements provided by a 
> ReversibleCollection. The "arbitrary" notion is captured by "getAny" so 
> that's what I'll use as a working term. (Of course any actual API we might 
> add would have a better name if we can find one.)
> 
> For a historical perspective, let's dig into the bug database and take a look 
> at this bug:
> 
> https://bugs.openjdk.java.net/browse/JDK-4939317
> 
> This requests a method Collection.get(Object). This searches the collection 
> for an element that equals() the argument and returns the element, or it 
> returns null if the element isn't found. (Recall in those days it was 
> impossible to add a method to an interface without breaking compatibility, so 
> it also proposes various workarounds that are no longer necessary.)
> 
> There's a comment from Josh Bloch saying that Collection should have had a 
> get() method as well as a no-arg remove() method. Well ok then! And he points 
> to the then-new Queue interface that was delivered in Java SE 5. Queue adds 
> the following methods that seem relevant to this discussion:
> 
> * E element() -- gets the head element, throws NSEE if empty
> * E remove() -- removes and returns the head element, throws NSEE if empty
> 
> (It also adds peek() and poll(), which are similar to the above except they 
> return null if empty.)
> 
> This is kind of odd, in that none of these methods satisfy what the bug's 
> submitter was requesting, which is a one-arg get() method. Also, these 
> methods are on Queue, which doesn't really help with collections in general.
> 
> You're asking for something that's somewhat different, which you called the 
> "find the first element when there is only one" problem. Here, there's a 
> precondition that the collection have a single element. (It's not clear to me 
> what should happen if the collection has zero or more than one element.)
> 
> To throw a couple more variations into the mix, Guava has a couple Collectors 
> (for streams) that do interesting things. The class is MoreCollectors:
> 
> https://guava.dev/releases/30.1.1-jre/api/docs/com/google/common/collect/MoreCollectors.html
> 
> and the collectors are:
> 
> * onlyElement -- if source has 1 element, returns it; throws NSEE if empty, 
> IAE if > 1
> * toOptional -- if source has 0 or 1 elements, returns an Optional; otherwise 
> throws
> 
> These apply to streams, but I think you can see the applicability to 
> Collection as well. In particular, your proposal is similar to what 
> onlyElement would look like if it were on Collection.
> 
> Let's summarize the variations so far:
> 
> * preconditions: exactly one element, at-most-one, at-least-one
> * behavior if preconditions not met: return null, return empty Optional, throw
>   exception
> * remove element or just peek
> * match a particular element, or return an arbitrary element
> 
> That's a lot of variations!
> 
> Before we talk about specific APIs, though, I wanted to talk about use cases. 
> Which of these variations are more useful or less useful? Which are likely to 
> appear in code? Henri gave a fairly specific example with a reason

Re: New convenience methods on Stream

2021-04-28 Thread Donald Raab
I looked through a few libraries and found some methods where the option #2 
proposal for Steam might be useful. If the JDK had constructors for ArrayList, 
HashSet and other collection types that take arrays this method would work 
there as well. 

> default > R to(Function function)
> {
>return function.apply((T[]) this.toArray());
> }


// JDK 
Set set = stream.to(Set::of);
List list = stream.to(List::of);
List arraysAsList = stream.to(Arrays::asList);

// Guava
ArrayList arrayList = stream.to(Lists::newArrayList);
HashSet hashSet = stream.to(Sets::newHashSet);
Multiset multiset = stream.to(ImmutableMultiset::copyOf);
List guavaList = stream.to(ImmutableList::copyOf);
Set guavaSet = stream.to(ImmutableSet::copyOf);

// Apache Commons Collections
FluentIterable fluentIterable = stream.to(FluentIterable::of);

// Eclipse Collections
MutableList adapter = stream.to(ArrayAdapter::adapt);

MutableList mutableList = stream.to(Lists.mutable::with);
MutableSet mutableSet = stream.to(Sets.mutable::with);
MutableBag mutableBag = stream.to(Bags.mutable::with);

// Eclipse Collections - ListIterable, SetIterable and Bag all extend Iterable, 
not Collection
ListIterable listIterable = stream.to(Lists.mutable::with);
SetIterable setIterable = stream.to(Sets.mutable::with);
Bag bag = stream.to(Bags.mutable::with);

// Eclipse Collections - Immutable Collections do not extend Collection
ImmutableList immutableList = stream.to(Lists.immutable::with);
ImmutableSet immutableSet = stream.to(Sets.immutable::with);
ImmutableBag immutableBag = stream.to(Bags.immutable::with);

// Eclipse Collections - Stack does not extend Collection
StackIterable stackIterable = stream.to(Stacks.mutable::with);
MutableStack mutableStack = stream.to(Stacks.mutable::with);
ImmutableStack immutableStack = stream.to(Stacks.immutable::with);

// Eclipse Collections - Mutable Map and MutableBiMap are both Iterable so 
they are valid returns
MutableMap map =
stream.to(array -> ArrayAdapter.adapt(array)
.toMap(String::toLowerCase, String::toUpperCase));

MutableBiMap biMap =
stream.to(array -> ArrayAdapter.adapt(array)
.toBiMap(String::toLowerCase, String::toUpperCase));

Thanks,
Don

> On Apr 27, 2021, at 1:35 AM, Donald Raab  wrote:
> 
> I realized after sending that option 2 can be made more abstract:
> 
> default > R to(Function function)
> {
>return function.apply((T[]) this.toArray());
> }
> 
>> 
>> 2. Pass the result of toArray directly into a function that can then return 
>> a Collection. This should work with Set.of, List.of and any 3rd party 
>> collections which take arrays.
>> 
>> default > R to(Function function)
>> {
>>   return function.apply((T[]) this.toArray());
>> }
>> 
>> Usage Examples:
>> 
>> Set set = stream.to(Set::of);
>> List list = stream.to(List::of);
>> List arrayList = stream.to(Arrays::asList);
>> 
> 



Re: New convenience methods on Stream

2021-04-26 Thread Donald Raab
I realized after sending that option 2 can be made more abstract:

default > R to(Function function)
{
return function.apply((T[]) this.toArray());
}

> 
> 2. Pass the result of toArray directly into a function that can then return a 
> Collection. This should work with Set.of, List.of and any 3rd party 
> collections which take arrays.
> 
> default > R to(Function function)
> {
>return function.apply((T[]) this.toArray());
> }
> 
> Usage Examples:
> 
> Set set = stream.to(Set::of);
> List list = stream.to(List::of);
> List arrayList = stream.to(Arrays::asList);
> 



New convenience methods on Stream

2021-04-26 Thread Donald Raab
Hi all,

I’d like to propose adding one or two of the following methods on Stream to 
cover more surface area of the Collections ecosystem, without requiring a big 
increase in the size of the Stream interface. Apologies if this has come up for 
discussion before. 


1. Stream contents into a mutable collection created by a Supplier. 

default > R toCollection(Supplier supplier)
{
return this.collect(Collectors.toCollection(supplier));
}

Usage Examples:

HashSet set = stream.toCollection(HashSet::new);
TreeSet sortedSet = stream.toCollection(TreeSet::new);
ArrayDeque deque = stream.toCollection(ArrayDeque::new);


2. Pass the result of toArray directly into a function that can then return a 
Collection. This should work with Set.of, List.of and any 3rd party collections 
which take arrays.

default > R to(Function function)
{
return function.apply((T[]) this.toArray());
}

Usage Examples:

Set set = stream.to(Set::of);
List list = stream.to(List::of);
List arrayList = stream.to(Arrays::asList);


3. Stream contents directly into any mutable collection. This would then work 
with all other mutable JDK Collection types.

default > R into(R result)
{
Collections.addAll(result, (T[]) this.toArray());
return result;
}

Usage Examples:

HashSet set = stream.into(new HashSet<>());
TreeSet sortedSet = stream.into(new 
TreeSet<>(Comparator.reverseOrder()));
CopyOnWriteArrayList list = stream.into(new CopyOnWriteArrayList<>());


There may be better default implementations, but I just wanted something here 
for illustrative purposes.

Thoughts?

Don

Re: ReversibleCollection proposal

2021-04-17 Thread Donald Raab


> I'm also concerned about conflicts over the other method names; something 
> like addFirst() is a pretty obvious method to add to a custom List 
> implementation. I haven't seen any, but that doesn't mean there aren't any.
> 
> s'marks

The getFirst() and getLast() methods will have collisions. We have both these 
methods on Eclipse Collections, and thankfully they return the same type. It is 
possible that someone decided to implement these methods and return Optional.

Re: ReversibleCollection proposal

2021-04-17 Thread Donald Raab
Hi, Stuart, happy to help.

I took a look at Groovy and Kotlin. Groovy has reverse() [1] and Kotlin has 
reversed() [2] and asReversed() [3] and reverse() [4]. I’m not quite familiar 
enough with Kotlin to know whether the reversed() method will collide.

[1] 
http://docs.groovy-lang.org/latest/html/groovy-jdk/java/util/List.html#reverse()
[2] 
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/reversed.html
[3] 
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/as-reversed.html
[4] https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/reverse.html

> On Apr 17, 2021, at 2:24 PM, Stuart Marks  wrote:
> 
> 
> 
> On 4/16/21 3:06 PM, Donald Raab wrote:
>> We should be cautious when adding new APIs to existing interfaces. There may 
>> be libraries which extend JDK types and already have reversed(), 
>> toReversed() or asReversed() APIs and corresponding interfaces.
>> There are OrderedIterable and ReversibleIterable interfaces and asReversed() 
>> and toReversed() methods in the Eclipse Collections API.
> 
> Hi Don, thanks for looking at the proposal.
> 
> Certainly a lot of care is required when introducing new interfaces, new 
> methods on existing interfaces, and covariant overrides. I believe the 
> primary risks are with name clashes and with return type mismatches.
> 
> On name clashes, "reversed" seems to thread the needle well, as I cannot find 
> any methods with that exact name on Eclipse Collections, Guava, Apache 
> Commons Collections, or several others. There are methods with similar names, 
> of course, such as "reverse", "asReversed", and "toReversed" but they 
> shouldn't cause name conflicts.
> 
> (There might be semantic conflicts, such as creating an reversed copy instead 
> of a reversed view, but I don't think that can be helped.)
> 
> If there are no name clashes with "reversed", the covariant overrides in the 
> sub-interfaces won't a problem. The covariant overrides on existing methods 
> (such as LinkedHashMap.entrySet) are a greater danger, I think. There are a 
> lot of LinkedHashMap subclasses (several dozen are visible in the Yawkat 
> browser) but only one had a conflicting override. It's trivially fixable, but 
> nonetheless it's an incompatibility.
> 
> I'm also concerned about conflicts over the other method names; something 
> like addFirst() is a pretty obvious method to add to a custom List 
> implementation. I haven't seen any, but that doesn't mean there aren't any.
> 
> s'marks



Re: ReversibleCollection proposal

2021-04-16 Thread Donald Raab
Hi Stuart,

We should be cautious when adding new APIs to existing interfaces. There may be 
libraries which extend JDK types and already have reversed(), toReversed() or 
asReversed() APIs and corresponding interfaces.

There are OrderedIterable and ReversibleIterable interfaces and asReversed() 
and toReversed() methods in the Eclipse Collections API. 

JavaDoc:
https://www.eclipse.org/collections/javadoc/10.4.0/org/eclipse/collections/api/ordered/OrderedIterable.html
https://www.eclipse.org/collections/javadoc/10.4.0/org/eclipse/collections/api/ordered/ReversibleIterable.html

Code Browser:
https://code.yawk.at/org.eclipse.collections/eclipse-collections-api/10.4.0/org/eclipse/collections/api/ordered/OrderedIterable.java
https://code.yawk.at/org.eclipse.collections/eclipse-collections-api/10.4.0/org/eclipse/collections/api/ordered/ReversibleIterable.java

OrderedIterable and ReversibleIterable appear in the mind map for RichIterable 
and have corresponding primitive versions in the mind map for PrimitiveIterable.

https://medium.com/oracledevs/visualizing-eclipse-collections-646dad9533a9?source=friends_link&sk=3370a5e8bb5a516e6b5d7040f7d0955b
 

There are methods asReversed() and toReversed() on ReversibleIterable().

https://code.yawk.at/org.eclipse.collections/eclipse-collections-api/10.4.0/org/eclipse/collections/api/ordered/ReversibleIterable.java#org.eclipse.collections.api.ordered.ReversibleIterable%23asReversed%28%29
https://code.yawk.at/org.eclipse.collections/eclipse-collections-api/10.4.0/org/eclipse/collections/api/ordered/ReversibleIterable.java#org.eclipse.collections.api.ordered.ReversibleIterable%23toReversed%28%29

The toReversed() method is co-variantly overridden in subtypes of 
ReversibleIterable. 

https://code.yawk.at/org.eclipse.collections/eclipse-collections-api/10.4.0/org/eclipse/collections/api/list/ListIterable.java#org.eclipse.collections.api.list.ListIterable%23toReversed%28%29

The asReversed() method returns a ReverseIterable which is lazy.

https://code.yawk.at/org.eclipse.collections/eclipse-collections/9.2.0/org/eclipse/collections/impl/lazy/ReverseIterable.java

Thanks,
Don



> On Apr 16, 2021, at 1:40 PM, Stuart Marks  wrote:
> 
> This is a proposal to add a ReversibleCollection interface to the Collections 
> Framework. I'm looking for comments on overall design before I work on 
> detailed specifications and tests. Please send such comments as replies on 
> this email thread.
> 
> Here's a link to a draft PR that contains the code diffs. It's prototype 
> quality, but it should be good enough to build and try out:
> 
>https://github.com/openjdk/jdk/pull/3533
> 
> And here's a link to a class diagram showing the proposed additions:
> 
> https://cr.openjdk.java.net/~smarks/ReversibleCollection/ReversibleCollectionDiagram.pdf
> 
> Thanks,
> 
> s'marks
> 
> 
> # Ordering and Reversibility
> 
> 
> A long-standing concept that's been missing from collections is that of the 
> positioning, sequencing, or arrangement of elements as a structural property 
> of a collection. (This is sometimes called the "iteration order" of a 
> collection.) For example, a HashSet is not ordered, but a List is. This 
> concept is mostly not manifested in the collections API.
> 
> Iterating a collection produces elements one after another in *some* 
> sequence. The concept of "ordered" determines whether this sequence is 
> defined or whether it's a coincidence of implementation. What does "having an 
> order" mean? It implies that there is a first element and that each element 
> has a successor. Since collections have a finite number of elements, it 
> further implies that there is a last element that has no successor. However, 
> it is difficult to discern whether a collection has a defined order. HashSet 
> generally iterates its elements in the same undefined order, and you can't 
> actually tell that it's not a defined order.
> 
> Streams do have a notion of ordering ("encounter order") and this is 
> preserved, where appropriate, through the stream pipeline. It's possible to 
> detect this by testing whether its spliterator has the ORDERED 
> characteristic. Any collection with a defined order will have a spliterator 
> with this characteristic. However, this is quite a roundabout way to 
> determine whether a collection has a defined order. Furthermore, knowing this 
> doesn't enable any additional operations. It only provides constraints on the 
> stream's implementations (keeping the elements in order) and provides 
> stronger semantics for certain operations. For example, findFirst() on an 
> unordered stream is the same as findAny(), but actually finds the first 
> element if the stream is ordered.
> 
> The concept of ordering is thus present in the system but is surfaced only in 
> a fairly indirect way. We can strengthen abstraction of ordering by making a 
> few observations and considering their implications.
> 
> Given that there is a first element and a last element, th

RFR: 8180352: Add Stream.toList() method

2021-02-04 Thread Donald Raab
I have been reading previous threads, the original bug request, and exploring 
the javadoc and implementation of toList() on Stream in JDK 16. I don’t want to 
waste time rehashing previous discussions, but I want to understand and 
prioritize the motivation of this change, and propose what I believe is a safer 
alternative name for this method based on the current implementation: 
Stream.toUnmodifiableList().


The original justification for Stream.toList() as described in the bug request 
filed in 2017 was: "It makes the coding just easier, saves some time and place.”

see: https://bugs.openjdk.java.net/browse/JDK-8180352 


Summarizing my interpretation of the eventual motivations for the change, over 
various threads (please correct as needed):

1. Convenience (easier to discover API and less code for developers to write - 
potential refactoring of large number of unmodifiable usages of 
Collectors.toList())
2. Performance (less copying during parallel operations and pre-sizing for 
serial operations)
3. Unmodifiability (List result but thread-safe and usable in static contexts 
and as hash keys - unsafe for mutating operations)
4. Support for nulls (Inconsistent with Collectors.toUnmodifiableList() but 
consistent with other Stream methods)


List is a mutable interface. It’s contract allows for “conditional mutability”. 
A convention was established in 2014 with Collectors.toList() returning a 
mutable List (ArrayList). The spec in Collectors.toList() says there is no 
guarantee on the type, but there appears to be no appetite to undo this. The 
current Collectors.toList() convention was then further strengthened with the 
addition of Collectors.toUnmodifiableList() in Java 10 in 2018. 


The problem as I see it is that we are currently only looking inside of the JDK 
for convention. The convention of using the toList() name has existed in other 
libraries and application code for as long as List has been available as an 
interface. The problem of inconsistency of convention within the JDK itself can 
be argued away as a historical mistake, but harder to claim for the 
"established convention" in other libraries.


We learned an important lesson in JDK 15 when the addition of a default isEmpty 
method to CharSequence broke Eclipse Collections. See this excellent blog from 
Stuart Marks: 
https://stuartmarks.wordpress.com/2020/09/22/incompatibilities-with-jdk-15-charsequence-isempty/.


The Stream interface has existed for seven years now. The method toList() is a 
prime candidate for anyone who wanted to optimize for convenience over the past 
seven years.


I found this default definition of toList() on the Folds interface in Cyclops:
https://github.com/aol/cyclops/blob/dc26bdad0f2b8a5aa87de4aa946ed52a0dbf00de/cyclops/src/main/java/com/oath/cyclops/types/foldable/Folds.java#L99


I discovered these occurrences of toList() in the jOOQ library.
https://github.com/jOOQ/jOOL/blob/main/jOOL/src/main/java/org/jooq/lambda/Collectable.java#L682
 
https://github.com/jOOQ/jOOL/blob/main/jOOL/src/main/java/org/jooq/lambda/SeqImpl.java#L557
 


Incidentally, this interface also defines toUnmodifiableList().
https://github.com/jOOQ/jOOL/blob/main/jOOL/src/main/java/org/jooq/lambda/Collectable.java#L692
 


jOOQ did not define it’s toList() method as a default method on Collectable, so 
it should not trigger the default method issue we discovered in JDK 15, but the 
specifications of toList() on Collectable and Stream are incompatible. Which 
specification should win?


Eclipse Collections consistent convention in all of its interfaces is that 
toList returns a MutableList [1]. MutableList extends java.util.List, which is 
a mutable interface. The Stream.toList() method will violate the principle of 
least surprise for Eclipse Collections users based on long time established 
conventions for the the library. There may be other libraries and applications 
that have defined their own toList() methods to return List as well.


Lastly, Craig Motlin explains the performance optimization we use for toList in 
our parallel implementation in his talk in 2014: 
https://www.infoq.com/presentations/java-streams-scala-parallel-collections/


Thanks,
Don


[1] Example usages of Eclipse Collections toList:


// toList result is mutable for all of these usages with Eclipse Collections
List list1 = mutableSet.toList();
List list2 = mutableSet.asLazy().toList(); 
List list3 = mutableSet.asParallel(Executors.newWorkStealingPool(), 
10).toList();
List list4 = mutableSet.stream().collect(Collectors.toList());
List list5 = mutableSet.stream().collect(Collectors2.toList());


// toList result is currently unmodifiable in Stream
List list6 = mutableSet.stream().toList();