It sounds like you're after some generalized notion of "consistency", and the fact that offer*() return a boolean whereas add*() do not seems inconsistent. Unfortunately, the methods have different semantics. After add(obj), obj is *always* a member of the collection, whereas after offer*(obj), obj *might or might not* be a member of the collection. The semantics of addFirst/addLast are more closely aligned with those of the add*() methods on Collection, so I decided to promote addFirst/addLast to SequencedCollection instead of offerFirst/offerLast.

On your other point, if you want to compare elements of a SequencedCollection to those of another, in encounter order, you can just use regular Iterators for that. ListIterator isn't necessary.

s'marks

On 10/5/22 3:36 PM, Ernie Rael wrote:
On 10/5/22 9:34 AM, Stuart Marks wrote:


On 10/4/22 9:38 PM, Ernie Rael wrote:
Summary of key points (maybe the mail was TL;DR)

OK thanks, I was still mulling over the previous email wondering which parts were significant enough to reply to.

  * SequencedCollection methods addFirst,addLast are the only methods in
    Collection hierarchy (AFAIK) that might modify the collection and do
    not return/signal if the collection was modified. Seems like
    offerFirst,offerLast are more consistent with Collections and still
    avoid method proliferation.

The problem is that the boolean return values from add() and from offerX() mean different things, and having them be adjacent on List would be confusing. (Yes, they're both present on Deque, which is one of the reasons Deque is too complicated.) And we couldn't adjust the semantics of SequencedCollection.offerX() to match add(), as that would clash with the existing semantics on Deque.


It is not uncommon for a sub-interface/sub-class to clarify the precise meaning of a method. If there's a general definition of SequencedCollection.offerFirst(e), then the Deque definition clarifies the meaning in that context. Consider:

Collections.add(e) - Returns true if this collection changed as a result of the 
call
List.add(e) - Return true
Set.add(e) - Returns true if this set did not already contain the specified 
element



From your other messages it seems like you want the boolean return value in order to keep track of whether the collection changed such that it affects equals() and hashCode().


No, I was just discussing the relationship of change and equals() when working with a SequencedCollection; it's more observations around using SeqCol. It's interesting that an addAll() can permute the structure, and end up at the same place.


There are other methods that might modify collections where you can't tell whether they actually modified the collection; consider Collection::clear or List::replaceAll.


I'll be more precise: methods that work with a single item return/signal change; most bulk operators such as removeif(), retainAll(), removeAll(), addAll() also return/signal change.

My main point is that "void SequencedCollection.addFirst(e)" is inconsistent with Collections' design. clear() is, well, clear(). replaceAll() seems to be an exception (a lone exception?).


So I don't think the boolean return value from add/offer is really buying you all that much.


When I put together a class based on a Collection, I like to follow the general design pattern. Not sure if/when I may have used the "return change" when using a collection. But when sub-classing a collection, since everything does it, so do I; I'll return change in any additional methods I might add. Consistent, least surprise...



  * With LinkedHashSet, seems like listIterator is missing. Rather than
    making that part of SequencedCollection, maybe an "interface
    ListIterable { ListIterator listIterator(int index); }". In addition
    to modification, bi-directional might also be optional, to support
    it's use with list equals.

ListIterator has indexes and so it's pretty much tied to List. Maybe what you're missing from LinkedHashSet


I want to be able to do List.equals(SequencedCollection) and vice versa (in particular with LinkedHashSet). In the list equals() implementations I've looked at, they all use two ListIterator to do the compare; only forward iteration.  For equals(), I think can wrap the SequencedCollection iterator in a forward, uni-directional listIterator, a little messy, and use that for equals(); support from the infrastructure would be nice. Which is where the idea of "ListIterator Collections.listIterator(iterator, index)" in the other email comes from.

Some daydreaming: For equals(), indexes don't matter except for initialization. And as far as "index ... tied to list", if SequencedCollection had a listIterator, I think I could form sub-sequence from that, with only forward iteration. But sub-SequencedCollection is a different topic. My usage requirement now is for equals().

Lists may include index, but they don't really depend on it unless they're RandomAccess; consider LinkedList. I don't see how indexes have a bearing on this discussion; in a listIterator the index is contained/maintained in the iterator.


is the ability to insert or reposition an element somewhere in the middle? This is certainly a valid use case, but it's quite rare, and I'm not sure I'd want to support it using an Iterator style mechanism anyway.

Surveying the usages of ListIterator,


The implicit use by equals() is missing.


I found that it's mainly used for iteration in reverse, and secondarily for replacing all the elements of a List (somewhat supplanted by replaceAll). We did run into one case where ListIterator was used to edit items within a list but it turned out to be INCREDIBLY clumsy to use. So if we want to support that (fairly rare) use case, I wouldn't start with a ListIterator or some variant with indexes removed. I'd also want to see use cases before considering adding new mechanisms.

s'marks


Reply via email to