[swift-evolution] [Draft] Rationalizing Sequence end-operation names
Brent Royal-Gordon
brent at architechies.com
Thu Jun 23 02:19:59 CDT 2016
As previously threatened mentioned, I've written a draft proposal to fix a number of naming issues with APIs operating on the beginning and end of Sequences and Collections:
• Inconsistent use of `prefix`/`suffix` vs. `first`/`last`
• Confusing naming of `drop` methods
• Ambiguous naming of `index(of:/where:)` and `drop(while:)`
• `prefix(upTo:)`, `prefix(through:)`, and `suffix(from:)` shouldn't be part of this family at all
To fix this, I propose:
• Renaming all methods which operate on more than one element at the beginning/end to use "prefix" or "suffix", not "first" or "last"
• Renaming `index(of:/where:)` to `earliestIndex(…)` and `first(where:)` to `earliest(where:)`
• Renaming the `drop` methods to use `removing`
• Redesigning `prefix(upTo:)`, `prefix(through:)` and `suffix(from:)` as subscripts with "partial" ranges, like `people[..<idx]` or perhaps `people[nil..<idx]`.
Since that last point requires significant redesign, including the introduction of new types, I have also included an alternative design which uses `people[to: idx]` instead.
This proposal does not seek to add new functionality; it merely renames or (in the case of the "aggressive" subscript option) redesigns existing functionality. I do, however, discuss (without making many judgements about their wisdom) how these changes might affect the naming of functionality we might add in future versions of Swift.
I would mainly like feedback on the two most open questions left in this proposal:
• The choice of `removing` to replace `drop`
• The decision about whether to use `people[..<idx]`, `people[nil..<idx]`, or `people[to: idx]`.
But I'd also like comments on the rest of the proposal, and on whether I should split the prefix(upTo:/through:)/suffix(from:) changes into a separate proposal from the rest.
I suspect this will cause a firestorm of bikeshedding, so please try to keep your suggestions grounded. Don't just suggest a name; articulate why it's a better choice than what we already have or what this proposal suggests. Only you can prevent our first *three*-hundred-message bikeshedding thread.
Thanks for your attention!
(P.S. The proposal below includes several huge tables which may cause some mail clients to become very pouty and refuse to eat their supper. You may want to read the proposal at <https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430 <https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430>> instead.)
Rationalizing Sequence end-operation names
Proposal: SE-NNNN <https://gist.github.com/brentdax/NNNN-sequence-end-op.md>
Author: Brent Royal-Gordon <https://github.com/brentdax>
Status: Draft
Review manager: TBD
<https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430#introduction>Introduction
Sequence and Collection offer many special operations which access or manipulate its first or last elements, but they are plagued by inconsistent naming which can make it difficult to find inverses or remember what the standard library offers. I propose that we standardize these names so they follow consistent, predictable patterns.
Swift-evolution thread: Pending <http://news.gmane.org/gmane.comp.lang.swift.evolution>
<https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430#scope>Scope
This proposal is not intended to add or remove any functionality; it merely renames and redesigns existing operations. Adding new operations is out of scope for this proposal unless it's incidental to the new designs.
Nonetheless, I do want the new designs to support adding more operations in the future. The names I've chosen are informed by some of the speculative APIs discussed in "Future directions", although I think they are perfectly sensible names even if nothing else changes.
<https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430#motivation>Motivation
The Sequence and Collection protocols offer a wide variety of APIs which are defined to operate on, or from, one end of the sequence:
Operand Get Index Exclude Remove (1) Pop (1) Equate (2)
Fixed Size
First 1 C.first - S.dropFirst() C.removeFirst() C.popFirst() -
Last 1 C.last - S.dropLast() C.removeLast() C.popLast() -
First (n: Int) S.prefix(_:) - S.dropFirst(_:) C.removeFirst(_:) - S.starts(with:)
...with closure S.prefix(while:) - S.drop(while:) - - S.starts(with:isEquivalent:)
Last (n: Int) S.suffix(_:) - S.dropLast(_:) C.removeLast(_:) - -
...with closure - - - - - -
Searching From End
First matching element - C.index(of:) - - - -
...with closure S.first(where:) C.index(where:) - - - -
Last matching element - - - - - -
...with closure - - - - - -
Based on Index
startIndex ..< (i: Index) C.prefix(upTo:) - - - - -
startIndex ... (i: Index) C.prefix(through:) - - - - -
(i: Index) ..< endIndex C.suffix(from:) - - - - -
I have included several blank rows for operands which fit the APIs' patterns, even if they don't happen to have any operations currently.
Type abbreviations:
S = Sequence
C = Collection (or a sub-protocol like BidirectionalCollection)
Notes:
remove and pop both mutate the array to delete the indicated element(s), but remove assumes as a precondition that the indicated elements exist, while pop checks whether or not they exist.
String and NSString have bespoke versions of first n and last n Equate operations, in the form of their hasPrefix and hasSuffix methods.
Leaving aside the question of whether any gaps in these tables ought to be filled, I see a number of issues with existing terminology.
<https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430#inconsistent-use-of-prefix-and-suffix>Inconsistent use of prefix and suffix
Some APIs which operate on a variable number of elements anchored at one end or the other use the terms prefix or suffix:
Sequence.prefix(_:) and Sequence.suffix(_:)
Sequence.prefix(while:)
String.hasPrefix(_:) and String.hasSuffix(_:)
Others, however, use first or last:
Sequence.dropFirst(_:) and Sequence.dropLast(_:)
Sequence.removeFirst(_:) and Sequence.removeLast(_:)
Still others use neither:
Sequence.starts(with:)
Sequence.drop(while:)
These methods are all closely related, but because of this inconsistent terminology, they fail to form predictable method families.
<https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430#first-has-multiple-meanings>first has multiple meanings
The word first can mean three different things in these APIs:
Just the very first element of the sequence.
A subsequence of elements anchored at the beginning of the sequence, as mentioned in the last point.
The first element encountered in the sequence which matches a given criterion when walking from the beginning of the sequence towards the end.
It would be nice to have more clarity here.
<https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430#drop-is-misleading-and-scary>drop is misleading and scary
In a Swift context, I believe the drop methods are actively confusing:
drop does not have the -ing or -ed suffix normally used for a nonmutating method.
drop has strong associations with destructive operations; it's the term used, for instance, for deleting whole tables in SQL. Even dropping would probably sound more like a mutating operation than alternatives.
As previously mentioned, the use of dropFirst and dropLast for single-drop operations and multiple-drop operations breaks up method families.
drop, dropFirst, and dropLast are terms of art, so we allow them a certain amount of leeway. However, I believe the drop functions go well beyond what we should permit. They are relatively uncommon operations, associated primarily with functional languages rather than mainstream object-oriented or imperative languages, and their violation of the normal Swift naming guidelines is especially misleading.
The term-of-art exception is not a suicide pact; it is meant to aid understanding by importing common terminology, not bind us to follow every decision made by any language that came before us. In this case, I think we should ignore precedent and forge our own path.
<https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430#unstated-direction-of-operation>Unstated direction of operation
Several APIs could theoretically be implemented by working from either end of the sequence, and would return different results depending on the direction, but do not indicate the direction in their names:
Sequence.drop(while:)
Collection.index(of:)
Adding a direction to these APIs would make their behavior clearer and permit us to offer opposite-end equivalents in the future. (Unmerged swift-evolution pull request 329 <https://github.com/apple/swift-evolution/pull/329> would add lastIndex methods.)
<https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430#the-index-base-name-has-been-polluted>The index(...) base name has been polluted
Swift 3's new collection model placed a number of low-level index manipulating operations on the base method name index. These now share that name with index(of:) and index(where:), which are much higher-level operations. This may be confusing for users looking for high-level operations; the only real relationship between the two sets of operations is that they both return an index.
It would be nice to separate these two groups of methods into different families.
<https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430#operations-taking-an-index-are-really-slicing>Operations taking an index are really slicing
prefix(upTo:), prefix(through:), and suffix(from:) at first appear to belong to the same family as the other prefix and suffix methods, but deeper examination reveals otherwise. They are the only operations which take indices, and they don't cleanly extend to the other operations which belong to these families. (For instance, it would not make sense to add a dropPrefix(upTo:) method; it would be equivalent to suffix(from:).)
Also, on Int-indexed collections like Array, prefix(_:) and prefix(upTo:) are identical, but there is little relationship between suffix(_:) and suffix(from:), which is confusing.
suffix(from:) is a particularly severe source of confusion. The other suffix APIs all have parameters relative to the endof the collection, but suffix(from:)'s index is still relative to the beginning of the array. This is obvious if you think deeply about the meaning of an index, but we don't really want to force our users to stare at a strange API until they have an epiphany.
I believe these operations have much more in common with slicing a collection using a range, and that reimagining them as slicing APIs will be more fruitful.
<https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430#why-does-it-matter>Why does it matter?
Many of these APIs are only occasionally necessary, so it's important that they be easy to find when needed and easy to understand when read. If you know that prefix(10) will get the first ten elements but don't know what its inverse is, you will probably not guess that it's dropFirst(10). The confusing, conflicting names in these APIs are a barrier to users adopting them where appropriate.
<https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430#proposed-solution>Proposed solution
We sever the index-taking APIs from the others, forming two separate families, which I will call the "Sequence-end operations" and the "index-based operations". We then consider and redesign them along separate lines.
<https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430#sequence-end-operations>Sequence-end operations
Each of these APIs should be renamed to use a directional word based on its row in the table:
Operand Directional word
Fixed Size
First 1 first
Last 1 last
First (n: Int) prefix
...with closure prefix
Last (n: Int) suffix
...with closure suffix
Searching From End
First matching element earliest
...with closure earliest
Last matching element latest
...with closure latest
To accomplish this, starts(with:) should be renamed to hasPrefix(_:), and other APIs should have directional words replaced or added as appropriate.
Additionally, the word drop in the "Exclude" APIs should be replaced with removing. These operations omit the same elements which the remove operations delete, so even though the types are not always the same (removing returns SubSequence, not Self), I think they are similar enough to deserve to be treated as nonmutating forms.
These changes yield (altered names bold):
Operand Get Index Exclude Remove (1) Pop (1) Equate (2)
Fixed Size
First 1 C.first - S.removingFirst() C.removeFirst() C.popFirst() -
Last 1 C.last - S.removingLast() C.removeLast() C.popLast() -
First (n: Int) S.prefix(_:) - S.removingPrefix(_:) C.removePrefix(_:) - S.hasPrefix(_:)
...with closure S.prefix(while:) - S.removingPrefix(while:) - - S.hasPrefix(_:isEquivalent:)
Last (n: Int) S.suffix(_:) - S.removingSuffix(_:) C.removeSuffix(_:) - -
...with closure - - - - - -
Searching From End
First matching element - C.earliestIndex(of:) - - - -
...with closure S.earliest(where:) C.earliestIndex(where:) - - - -
Last matching element - - - - - -
...with closure - - - - - -
<https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430#alternative-to-removing>Alternative to removing
If the type differences are seen as disqualifying removing as a replacement for drop, I suggest using skipping instead.
There are, of course, many possible alternatives to skipping; this is almost a perfect subject for bikeshedding. I've chosen skipping because:
It is not an uncommon word, unlike (say) omitting. This means non-native English speakers and schoolchildren are more likely to recognize it.
It is an -ing verb, unlike (say) without. This makes it fit common Swift naming patterns more closely.
It does not imply danger, unlike (say) dropping, nor some sort of ongoing process, unlike (say) ignoring. This makes its behavior more obvious.
If you want to suggest an alternative on swift-evolution, please do not merely mention a synonym; rather, explain why it is an improvement on either these axes or other ones. (I would be particularly interested in names other than removing which draw an analogy to something else in Swift.)
<https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430#index-based-operations>Index-based operations
Because these APIs look up elements based on their indices, I believe these operations should be exposed as subscripts, and ideally should look like other slicing operations.
My primary design is rather ambitious, introducing two new types and either two operator overloads, or four unary forms of existing binary operators. I therefore present a more conservative alternative as well.
<https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430#preferred-ambitious-option>Preferred (ambitious) option
let head = people[..<i]
let tail = people[i..<]
let rearrangedPeople = tail + head
Or this small variation:
let head = people[nil ..< i]
let tail = people[i ..< nil]
let rearrangedPeople = tail + head
The operators would construct instances of a new pair of types, IncompleteRange (for ..<) and IncompleteClosedRange (for ...), and Collection would include new subscripts taking these types. These would probably have default implementations which constructed an equivalent Range or ClosedRange using startIndex and endIndex, then passed the resulting range through to the existing subscripts.
I prefer this option because it offers an elegant syntax immediately recognizable as a form of slicing, and provides a straightforward way for a future version of Swift to extend other Range-handling Collection operations, like replaceSubrange(_:with:) and removeSubrange(_:), to handle subranges bound by the ends of the Collection.
<https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430#alternative-conservative-option>Alternative (conservative) option
let head = people[to: i]
let tail = people[from: i]
let rearrangedPeople = tail + head
This would be a simple changing of the methods into subscripts; no additional types or operators would be needed. I have changed upTo: into just to: to match the pattern set by stride(from:to:by:).
<https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430#detailed-design>Detailed design
Draft note: This section is a mere sketch and should probably be expanded before submitting for a pull request.
<https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430#sequence-end-operations-1>Sequence-end operations
The following methods should be renamed as follows wherever they appear in the standard library. These are simple textual substitutions; we propose no changes whatsoever to types, parameter interpretations, or other semantics.
Old method New method
dropFirst() -> SubSequence removingFirst() -> SubSequence
dropLast() -> SubSequence removingLast() -> SubSequence
dropFirst(_ n: Int) -> SubSequence removingPrefix(_ n: Int) -> SubSequence
drop(@noescape while predicate: (Iterator.Element) throws -> Bool) rethrows -> SubSequence removingPrefix(@noescape while predicate: (Iterator.Element) throws -> Bool) rethrows -> SubSequence
dropLast(_ n: Int) -> SubSequence removingSuffix(_ n: Int) -> SubSequence
removeFirst(_ n: Int) removePrefix(_ n: Int)
removeLast(_ n: Int) removeSuffix(_ n: Int)
starts<PossiblePrefix: Sequence>(with possiblePrefix: PossiblePrefix) -> Bool where ... hasPrefix<PossiblePrefix: Sequence>(_ possiblePrefix: PossiblePrefix) -> Bool where ...
starts<PossiblePrefix : Sequence>(with possiblePrefix: PossiblePrefix, isEquivalent: @noescape (Iterator.Element, Iterator.Element) throws -> Bool) rethrows -> Bool where ... hasPrefix<PossiblePrefix : Sequence>(_ possiblePrefix: PossiblePrefix, isEquivalent: @noescape (Iterator.Element, Iterator.Element) throws -> Bool) rethrows -> Bool where ...
first(where predicate: @noescape (Iterator.Element) throws -> Bool) rethrows -> Iterator.Element? earliest(where predicate: @noescape (Iterator.Element) throws -> Bool) rethrows -> Iterator.Element?
index(of element: Iterator.Element) -> Index? earliestIndex(of element: Iterator.Element) -> Index?
index(where predicate: @noescape (Iterator.Element) throws -> Bool) rethrows -> Index? earliestIndex(where predicate: @noescape (Iterator.Element) throws -> Bool) rethrows -> Index?
<https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430#index-based-operations-1>Index-based operations
<https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430#preferred-ambitious-option-1>Preferred (ambitious) option
Implementation would be roughly as follows:
Add a pair of new types, IncompleteRange and IncompleteClosedRange, with definitions like:
struct IncompleteRange<Bound: Comparable> {
var lowerBound: Bound?
var upperBound: Bound?
func completed(with bounds: Range<Bound>) -> Range<Bound> {
return (lowerBound ?? bounds.lowerBound) ..< (upperBound ?? bounds.upperBound)
}
}
// And likewise for `IncompleteClosedRange`
Either add prefix and postfix ..< and ... operators, or overload the existing infix ..< and ... operators, to construct an IncompleteRange or IncompleteClosedRange. For the prefix/postfix operators, this would look like:
postfix func ..< <Bound: Comparable>(lowerBound: Bound) -> IncompleteRange<Bound> {
return IncompleteRange(lowerBound: lowerBound, upperBound: nil)
}
prefix func ..< <Bound: Comparable>(upperBound: Bound) -> IncompleteRange<Bound> {
return IncompleteRange(lowerBound: nil, upperBound: upperBound)
}
// And likewise for `...` and `IncompleteClosedRange`
Add a pair of IncompleteRange and IncompleteClosedRange subscripts to Collection:
extension Collection {
subscript (range: IncompleteRange<Index>) -> SubSequence {
return self[range.completed(with: startIndex ..< endIndex)]
}
// And likewise for `IncompleteClosedRange`
}
<https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430#alternative-conservative-option-1>Alternative (conservative) option
The three methods below will be replaced with equivalent subscripts in all types conforming to Collection or a sub-protocol:
Old method New subscript
prefix(upTo end: Index) -> SubSequence subscript(to end: Index) -> SubSequence
prefix(through position: Index) -> SubSequence subscript(through position: Index) -> SubSequence
func suffix(from start: Index) -> SubSequence subscript(from start: Index) -> SubSequence
<https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430#impact-on-existing-code>Impact on existing code
Obviously, any code using these APIs under their old names or designs would have to be transitioned to the new names and designs.
The sequence-end operations would be by far the simplest to handle; these are simple renamings and could be handed by @available(renamed:) and migration support. The only complication is that some overloads have transitioned to a new base name, while others have stayed with the old one, but I suspect the migrator is up to this task.
The preferred option for index-based operations is more difficult to migrate. The patterns would be roughly:
collection.prefix(upTo: i) => collection[..<i] or collection[nil ..< i]
collection.prefix(through: i) => collection[...i] or collection[nil ... i]
collection.suffix(from: i) => collection[i..<] or collection[i ..< nil]
A custom fix-it would be ideal, but is probably not absolutely necessary here; an @available(message:) would do in a pinch. Presumably this would have to be a special case in the migrator as well.
The alternative option for index-based operations would be simpler to fix-it and migrate—we need merely change references from the method to the subscript with the same (or similar, for upTo) parameter labels. I'm not sure if @available(renamed:) supports changing a method to a subscript.
<https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430#alternatives-considered>Alternatives considered
I considered many alternatives to removing or skipping, some of which are discussed in the section about them.
Rather than using earliest and latest, I considered using first and last for the "First matching" and "Last matching" categories. However, I didn't like the thought of overloading a property with a function, and I felt like earliest and latest actually have different semantics (searching for the match closest to a particular end) than first and last (manipulating the element at that end).
I considered using first and last as the basis for both single-element and multiple-element operations (such that prefix(3) would become first(3), etc.), but:
These seemed like distinct functionalities, particularly since their types are different.
I'm not comfortable with heavily overloading a property with a bunch of methods, and didn't want to make firstand last into methods.
Most APIs work fine, but hasFirst(_:) is atrocious, and I see no better alternative which includes the word first.
I considered moving first and last to Sequence and possibly making them methods, but my understanding is that the core team has considered and rejected this approach in the past.
I considered moving removingFirst and removingLast to Collection and making them properties, to match firstand last, but this seemed like the sort of foolish consistency that Ralph Waldo Emerson warned of.
<https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430#future-directions>Future directions
Note: The rest of this proposal is highly speculative and there's probably no need to read further.
<https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430#other-sequence-api-cleanups>Other Sequence API cleanups
<https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430#seriously-source-breaking>Seriously source-breaking
There is an ongoing discussion about which, if any, of map, flatMap, filter, and reduce ought to be renamed to more closely match Swift naming conventions. There is also discussion about relabeling many closure parameters.
The "Future directions" section below suggests every(where:) as an alternative to filter which could be extended in ways compatible with this proposal.
<https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430#significantly-source-breaking>Significantly source-breaking
The removeSubrange(_:) and replaceSubrange(_:with:) APIs are rather oddly named. They might be better renamed to, for instance, remove(in:) and replace(in:with:).
It is not clear how important removingFirst() and removingLast() actually are, given that they're exactly equivalent to removingPrefix(1) and removingSuffix(1), and their corresponding "get" members are on Collection instead of Sequence. They could be removed.
<https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430#slightly-source-breaking>Slightly source-breaking
removeFirst/Last() and popFirst/Last() are very nearly redundant; their only difference is that the removemethods have a non-Optional return type and require the collection not be empty, while the pop methods have an Optional return type and return nil if it's empty.
These operations could be merged, with the remove operations taking on the preconditions of the current popoperations; additionally, removePrefix(_:) and removeSuffix(_:) could drop their equivalent preconditions requiring that the elements being removed exist. These changes would simplify the standard library and make these methods more closely parallel the equivalent removing methods, which do not have similar preconditions.
Performance-critical code which wants to avoid the checks necessary to remove these preconditions could switch to remove(at:) and removeSubrange(_:), which would continue to reject invalid indices.
<https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430#adding-sequence-and-collection-operations>Adding sequence and collection operations
This exercise in renaming suggests all sorts of other APIs we might add, and a few we might rename.
In general, I have not attempted to carefully scrutinize the usefulness of each of these APIs; instead, I have merely listed the ones which I can imagine some kind of use for. The main exception is the "Pop" operation; I can imagine several different, and rather incompatible, ways to extend it, and I'm not going to take the time to sort out my thoughts merely to write a "Future directions" section.
<https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430#filling-in-the-sequence-end-api-table>Filling in the sequence-end API table
The gaps in the table suggest a number of APIs we could offer in the future. Here, I have filled in all options which are at least coherent:
Operand Get Index Exclude Remove (1) Pop (1) Equate (2)
Fixed Size
First 1 C.first C.firstIndex S.removingFirst() C.removeFirst() C.popFirst() -
Last 1 C.last C.lastIndex S.removingLast() C.removeLast() C.popLast() -
First (n: Int) S.prefix(_:) C.prefixIndex(_:) S.removingPrefix(_:) C.removePrefix(_:) - S.hasPrefix(_:)
...with closure S.prefix(while:) C.prefixIndex(while:) S.removingPrefix(while:) C.removePrefix(while:) - S.hasPrefix(_:isEquivalent:)
Last (n: Int) S.suffix(_:) C.suffixIndex(_:) S.removingSuffix(_:) C.removeSuffix(_:) - S.hasSuffix(_:)
...with closure S.suffix(while:) C.suffixIndex(while:) S.removingSuffix(while:) C.removeSuffix(while:) - S.hasSuffix(_:isEquivalent:)
Searching From End
First matching element S.earliest(_:) C.earliestIndex(of:) S.removingEarliest(_:) C.removeEarliest(_:) - -
...with closure S.earliest(where:) C.earliestIndex(where:) S.removingEarliest(where:) C.removeEarliest(where:) - -
Last matching element S.latest(_:) C.latestIndex(of:) S.removingLatest(_:) C.removeLatest(_:) - -
...with closure S.latest(where:) C.latestIndex(where:) S.removingLatest(where:) C.removeLatest(where:) - -
To explain a few entries which might not be immediately obvious: firstIndex and lastIndex would be nil if the collection is empty, and lastIndex would be the index before endIndex. prefixIndex would return the last index of the prefix, and suffixIndex would return the first index of the suffix; alternatively, these could be named with Indices and return ranges. earliest(_:) and latest(_:) would return the first and last element equal to the provided value; on a Set, they would be roughly equivalent to NSSet.member(_:).
The changes I consider most worthy include:
Adding corresponding last, suffix, and latest methods for all first, prefix, and earliest methods.
Adding corresponding while: versions of all appropriate prefix/suffix APIs.
Ones that could be useful, but can usually be emulated with more work:
Adding remove/removing-by-content APIs.
Adding prefix/suffixIndex(while:).
Ones that are mere conveniences or may not have strong use cases:
first/lastIndex and prefix/suffixIndex(_:).
earliest/latest(_:).
<https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430#all-and-every-as-operands>"All" and "Every" as operands
One could imagine adding rows to this table for "all" and "every matching". In addition to creating some useful new API, this would also suggest some interesting renaming for existing APIs:
allIndices would be a name for indices.
removeAll() is actually an existing name which happens to fit this pattern.
every(where:) would be a name for filter. Though I believe filter is a strong term of art, I do note that every(where:) does not cause confusion about the sense of its test, a major complaint about filter.
In the table below, bold indicates new functionality; italics indicates existing functionality renamed to fit this pattern.
Operand Get Index Exclude Remove (1) Pop (1) Equate (2)
Fixed Size
First 1 C.first C.firstIndex S.removingFirst() C.removeFirst() C.popFirst() -
Last 1 C.last C.lastIndex S.removingLast() C.removeLast() C.popLast() -
First (n: Int) S.prefix(_:) C.prefixIndex(_:) S.removingPrefix(_:) C.removePrefix(_:) - S.hasPrefix(_:)
...with closure S.prefix(while:) C.prefixIndex(while:) S.removingPrefix(while:) C.removePrefix(while:) - S.hasPrefix(_:isEquivalent:)
Last (n: Int) S.suffix(_:) C.suffixIndex(_:) S.removingSuffix(_:) C.removeSuffix(_:) - S.hasSuffix(_:)
...with closure S.suffix(while:) C.suffixIndex(while:) S.removingSuffix(while:) C.removeSuffix(while:) - S.hasSuffix(_:isEquivalent:)
All - allIndices - C.removeAll() - -
Searching From End
First matching element S.earliest(_:) C.earliestIndex(of:) S.removingEarliest(_:) C.removeEarliest(_:) - -
...with closure S.earliest(where:) C.earliestIndex(where:) S.removingEarliest(where:) C.removeEarliest(where:) - -
Last matching element S.latest(_:) C.latestIndex(of:) S.removingLatest(_:) C.removeLatest(_:) - -
...with closure S.latest(where:) C.latestIndex(where:) S.removingLatest(where:) C.removeLatest(where:) - -
Every matching element S.every(_:) C.everyIndex(of:) S.removingEvery(_:) C.removeEvery(_:) - -
...with closure S.every(where:) C.everyIndex(where:) S.removingEvery(where:) C.removeEvery(where:) - -
An alternative to the every methods is to give them names based on all or any, but these tend to require breaks from the naming patterns of the matching earliest and latest methods to remain grammatical.
<https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430#additional-index-based-operations>Additional index-based operations
Though accessing a range of elements bounded by the end of the collection is useful, it might be useful to extend that ability to other range-based collection APIs. IncompleteRange would make this especially easy; we would simply overload Range-taking APIs to permit IncompleteRanges as well. However, we could also provide variants of these APIs which take a to:, through:, or from: index parameter in place of an index range.
Candidates include:
MutableCollection.subscript(bounds: Range<Index>) { set }, making the subscripts in this proposal mutable.
RangeReplaceableCollection.removeSubrange(\_:)
RangeReplaceableCollection.replaceSubrange(\_:with:)
The various Range parameters in String (although these might be better replaced with slice-based APIs).
--
Brent Royal-Gordon
Architechies
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160623/923947a2/attachment-0001.html>
More information about the swift-evolution
mailing list