<div dir="ltr">The incomplete range concept is quite intriguing.<div><br></div><div>Have we considered spelling the operators with an asterisk at the incomplete end?</div><div>prefix *..<</div><div>prefix *...</div><div>postfix ...*</div><div>postfix ..<*</div><div><br></div><div>That way the use-sites would look like:</div><div>someCollection[*..<idx]</div><div>someCollection[*...idx]<br></div><div>someCollection[idx...*]<br></div><div>someCollection[idx..<*]</div><div><br></div><div>From a “first-glance” perspective, the asterisk “looks like” a wildcard placeholder, which should help readers and writers of code to understand the meaning.</div><div><br></div><div>And from a future language development standpoint, we’ll keep the triple-dot spelling available for whatever needs may arise (tuple splatting, variadic generics, etc.)</div><div><br></div><div>Thoughts?</div><div><br></div><div>Nevin</div><div><br></div></div><div class="gmail_extra"><br><div class="gmail_quote">On Fri, Jul 1, 2016 at 6:50 PM, Dave Abrahams via swift-evolution <span dir="ltr"><<a href="mailto:swift-evolution@swift.org" target="_blank">swift-evolution@swift.org</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><span class=""><br>
on Thu Jun 23 2016, Brent Royal-Gordon <<a href="mailto:swift-evolution@swift.org">swift-evolution@swift.org</a>> wrote:<br>
<br>
> As previously threatened mentioned, I've written a draft proposal to<br>
> fix a number of naming issues with APIs operating on the beginning and<br>
> end of Sequences and Collections:<br>
><br>
> • Inconsistent use of `prefix`/`suffix` vs. `first`/`last`<br>
> • Confusing naming of `drop` methods<br>
> • Ambiguous naming of `index(of:/where:)` and `drop(while:)`<br>
> • `prefix(upTo:)`, `prefix(through:)`, and `suffix(from:)` shouldn't<br>
> be part of this family at all<br>
><br>
> To fix this, I propose:<br>
><br>
> • Renaming all methods which operate on more than one element at the<br>
> beginning/end to use "prefix" or "suffix", not "first" or "last"<br>
> • Renaming `index(of:/where:)` to `earliestIndex(…)` and<br>
> `first(where:)` to `earliest(where:)`<br>
<br>
</span>What's wrong with firstIndex(of:/where:) [and lastIndex(of:/where:)]?<br>
That seems like a much less esoteric way to phrase it that meshes well<br>
with the meanings of<br>
<br>
xs.first<br>
xs.indices.first<br>
<br>
etc.<br>
<span class=""><br>
> • Renaming the `drop` methods to use `removing`<br>
<br>
</span>Very clever! I *like*.<br>
<span class=""><br>
> • Redesigning `prefix(upTo:)`, `prefix(through:)` and `suffix(from:)`<br>
> as subscripts with "partial" ranges, like `people[..<idx]` or perhaps<br>
> `people[nil..<idx]`.<br>
<br>
</span>Yes please; I really want this. This part is a slightly nontrivial<br>
design problem, though. Someone should build an implementation before<br>
the actual design is proposed. Probably the best way would be to<br>
leave prefix and suffix alone for the moment and add/test the new<br>
subscripts.<br>
<span class=""><br>
> Since that last point requires significant redesign, including the<br>
> introduction of new types, I have also included an alternative design<br>
> which uses `people[to: idx]` instead.<br>
<br>
</span>I really don't like using labels for this, because stride(to:) and<br>
stride(through:) have already spawned a naming bikeshed with no clear<br>
resolution, suggesting that no name works. Plus, the ..< operator<br>
already implies the name.<br>
<span class=""><br>
> This proposal does not seek to add new functionality; it merely<br>
> renames or (in the case of the "aggressive" subscript option)<br>
> redesigns existing functionality. I do, however, discuss (without<br>
> making many judgements about their wisdom) how these changes might<br>
> affect the naming of functionality we might add in future versions of<br>
> Swift.<br>
<br>
</span>Good.<br>
<span class=""><br>
> I would mainly like feedback on the two most open questions left in<br>
> this proposal:<br>
><br>
> • The choice of `removing` to replace `drop`<br>
<br>
</span>It's 100% appropriate, provided that the APIs match some corresponding<br>
mutating remove API. Nonmutating operations are often implemented via<br>
lazy adaptors... which a slice can be viewed to be. So I think this is<br>
a beautiful answer.<br>
<span class=""><br>
> • The decision about whether to use `people[..<idx]`,<br>
> `people[nil..<idx]`, or `people[to: idx]`.<br>
<br>
</span>I prefer how the first one reads.<br>
<span class=""><br>
> But I'd also like comments on the rest of the proposal, and on whether<br>
> I should split the prefix(upTo:/through:)/suffix(from:) changes into a<br>
> separate proposal from the rest.<br>
<br>
</span>I very much appreciate that you're addressing all of these at once.<br>
<span class=""><br>
> I suspect this will cause a firestorm of bikeshedding, so please try<br>
> to keep your suggestions grounded. Don't just suggest a name;<br>
> articulate why it's a better choice than what we already have or what<br>
> this proposal suggests. Only you can prevent our first<br>
> *three*-hundred-message bikeshedding thread.<br>
><br>
> Thanks for your attention!<br>
><br>
> (P.S. The proposal below includes several huge tables which may cause<br>
> some mail clients to become very pouty and refuse to eat their<br>
> supper. You may want to read the proposal at<br>
> <<a href="https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430" rel="noreferrer" target="_blank">https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430</a><br>
</span>> <<a href="https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430" rel="noreferrer" target="_blank">https://gist.github.com/brentdax/024d26c2b68b88323989540c06261430</a>>><br>
> instead.)<br>
<span class="">><br>
> The Sequence and Collection protocols offer a wide variety of APIs which<br>
> are defined to operate on, or from, one end of the sequence:<br>
><br>
> Operand Get Index Exclude Remove (1) Pop (1) Equate (2)<br>
<br>
</span>I think you want “Operation” or “Semantics” rather than “Operand” (which<br>
means an argument to an operation)<br>
<span class=""><br>
> Fixed Size<br>
> First 1 C.first - S.dropFirst() C.removeFirst() C.popFirst() -<br>
> Last 1 C.last - S.dropLast() C.removeLast() C.popLast() -<br>
> First (n: Int) S.prefix(_:) - S.dropFirst(_:) C.removeFirst(_:) - S.starts(with:)<br>
> ...with closure S.prefix(while:) - S.drop(while:) - - S.starts<br>
> (with:isEquivalent:)<br>
> Last (n: Int) S.suffix(_:) - S.dropLast(_:) C.removeLast(_:) - -<br>
> ...with closure - - - - - -<br>
> Searching From End<br>
</span>> First matching - C.index(of:) - - - -<br>
> element<br>
<span class="">> ...with closure S.first(where:) C.index(where:) - - - -<br>
> Last matching element - - - - - -<br>
> ...with closure - - - - - -<br>
> Based on Index<br>
> startIndex ..< (i: Index) C.prefix(upTo:) - - - - -<br>
> startIndex ... (i: Index) C.prefix(through:) - - - - -<br>
> (i: Index) ..< endIndex C.suffix(from:) - - - - -<br>
><br>
> I have included several blank rows for operands which fit the APIs' patterns, even if they don't happen to have any operations currently.<br>
><br>
> Type abbreviations:<br>
><br>
</span>> * S = Sequence<br>
> * C = Collection (or a sub-protocol like BidirectionalCollection)<br>
><br>
> Notes:<br>
><br>
> 1 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<br>
<span class="">> checks whether or not they exist.<br>
><br>
</span>> 2 String and NSString have bespoke versions of first n and last n Equate operations, in the form of their hasPrefix and hasSuffix methods.<br>
<span class="">><br>
> Leaving aside the question of whether any gaps in these tables ought to be filled, I see a number of issues with existing terminology.<br>
><br>
</span>> SVG ImageInconsistent use of prefix and suffix<br>
<span class="">><br>
> Some APIs which operate on a variable number of elements anchored at one end or the other use the terms prefix or suffix:<br>
><br>
</span>> * Sequence.prefix(_:) and Sequence.suffix(_:)<br>
> * Sequence.prefix(while:)<br>
> * String.hasPrefix(_:) and String.hasSuffix(_:)<br>
<span class="">><br>
> Others, however, use first or last:<br>
><br>
</span>> * Sequence.dropFirst(_:) and Sequence.dropLast(_:)<br>
> * Sequence.removeFirst(_:) and Sequence.removeLast(_:)<br>
><br>
> Still others use neither:<br>
><br>
> * Sequence.starts(with:)<br>
> * Sequence.drop(while:)<br>
<span class="">><br>
> These methods are all closely related, but because of this inconsistent terminology, they fail to form predictable method families.<br>
><br>
</span>> SVG Imagefirst has multiple meanings<br>
<span class="">><br>
> The word first can mean three different things in these APIs:<br>
><br>
</span>> * Just the very first element of the sequence.<br>
><br>
> * A subsequence of elements anchored at the beginning of the sequence,<br>
<span class="">> as mentioned in the last point.<br>
><br>
</span>> * The first element encountered in the sequence which matches a given<br>
<span class="">> criterion when walking from the beginning of the sequence towards the<br>
> end.<br>
><br>
> It would be nice to have more clarity here.<br>
<br>
</span>You seem to be suggesting that a word needs to mean exactly the same<br>
thing regardless of context. If so, I disagree. If I say “the first<br>
element” or “the first element greater than 5” there's absolutely no<br>
lack of clarity AFAICT. That accounts for the first and last bullets<br>
<br>
The usage in the middle bullet is open to misinterpretation and I would<br>
support fixing that.<br>
<br>
xs.removeFirst(42)<br>
<br>
could read like, “remove the first element equal to 42.”<br>
<br>
> SVG Imagedrop is misleading and scary<br>
<span class="">><br>
> In a Swift context, I believe the drop methods are actively confusing:<br>
><br>
</span>> * drop does not have the -ing or -ed suffix normally used for a<br>
> nonmutating method.<br>
><br>
> * drop has strong associations with destructive operations; it's the<br>
<span class="">> term used, for instance, for deleting whole tables in SQL. Even<br>
> dropping would probably sound more like a mutating operation than<br>
> alternatives.<br>
><br>
</span>> * As previously mentioned, the use of dropFirst and dropLast for<br>
<span class="">> single-drop operations and multiple-drop operations breaks up method<br>
> families.<br>
><br>
> drop, dropFirst, and dropLast are terms of art, so we allow them a<br>
> certain amount of leeway. However, I believe the drop functions go<br>
> well beyond what we should<br>
> permit. They are relatively uncommon operations, associated primarily<br>
> with functional languages rather than mainstream object-oriented or<br>
> imperative languages, and<br>
> their violation of the normal Swift naming guidelines is especially<br>
> misleading.<br>
><br>
> The term-of-art exception is not a suicide pact;<br>
<br>
</span>Tatoo that on your forehead, mister!<br>
<span class=""><br>
> it is meant to aid understanding by importing common terminology, not<br>
> bind us to follow every decision made by any language that came before<br>
> us. In this case, I think we should ignore precedent and forge our own<br>
> path.<br>
><br>
</span>> SVG ImageUnstated direction of operation<br>
<span class="">><br>
> Several APIs could theoretically be implemented by working from either<br>
> end of the sequence, and would return different results depending on<br>
> the direction, but do not indicate the direction in their names:<br>
><br>
</span>> * Sequence.drop(while:)<br>
> * Collection.index(of:)<br>
<span class="">><br>
> 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<br>
> request 329 would add lastIndex methods.)<br>
><br>
</span>> SVG ImageThe index(...) base name has been polluted<br>
<span class="">><br>
> Swift 3's new collection model placed a number of low-level index<br>
> manipulating operations on the base method name index. These now share<br>
> that name with index(of:) and index(where:), which are much<br>
> higher-level operations. This may be confusing for users looking for<br>
> high-level operations; the only real relationship between the two sets<br>
> of operations is that they both return an index.<br>
<br>
</span>There's another relationship. Once you call the high-level operation,<br>
you're now in the domain of indexing, and are very likely to ask for the<br>
index(after:) the one you found.<br>
<span class=""><br>
> It would be nice to separate these two groups of methods into<br>
> different families.<br>
<br>
</span>I used to think that was important, but I no longer do given the above.<br>
<br>
> SVG ImageOperations taking an index are really slicing<br>
<span class="">><br>
> prefix(upTo:), prefix(through:), and suffix(from:) at first appear to<br>
> belong to the same family as the other prefix and suffix methods, but<br>
> deeper examination reveals otherwise. They are the only operations<br>
> which take indices, and they don't cleanly extend to the other<br>
> operations which belong to these families. (For instance, it would not<br>
> make sense to add a dropPrefix(upTo:) method; it would be equivalent<br>
> to suffix(from:).)<br>
><br>
> Also, on Int-indexed collections like Array, prefix(_:) and<br>
> prefix(upTo:) are identical, but there is little relationship between<br>
> suffix(_:) and suffix(from:), which is confusing.<br>
><br>
> suffix(from:) is a particularly severe source of confusion. The other<br>
> suffix APIs all have parameters relative to the endof the collection,<br>
> but suffix(from:)'s index is still relative to the beginning of the<br>
> array. This is obvious if you think deeply about the meaning of an<br>
> index, but we don't really want to force our users to stare at a<br>
> strange API until they have an epiphany.<br>
><br>
> I believe these operations have much more in common with slicing a<br>
> collection using a range, and that reimagining them as slicing APIs<br>
> will be more fruitful.<br>
<br>
</span>Yes please.<br>
<br>
> SVG ImageWhy does it matter?<br>
<span class="">><br>
> Many of these APIs are only occasionally necessary, so it's important<br>
> that they be easy to find when needed and easy to understand when<br>
</span>> read. If you know that prefix (10) will get the first ten elements but<br>
<span class="">> don't know what its inverse is, you will probably not guess that it's<br>
> dropFirst(10). The confusing, conflicting names in these APIs are a<br>
> barrier to users adopting them where appropriate.<br>
><br>
</span>> SVG ImageProposed solution<br>
<span class="">><br>
> We sever the index-taking APIs from the others, forming two separate<br>
> families, which I will call the "Sequence-end operations" and the<br>
> "index-based operations". We then consider and redesign them along<br>
> separate lines.<br>
><br>
</span>> SVG ImageSequence-end operations<br>
<span class="">><br>
> Each of these APIs should be renamed to use a directional word based<br>
> on its row in the table:<br>
><br>
> Operand Directional word<br>
> Fixed Size<br>
> First 1 first<br>
> Last 1 last<br>
> First (n: Int) prefix<br>
> ...with closure prefix<br>
> Last (n: Int) suffix<br>
> ...with closure suffix<br>
> Searching From End<br>
> First matching element earliest<br>
> ...with closure earliest<br>
> Last matching element latest<br>
> ...with closure latest<br>
><br>
> To accomplish this, starts(with:) should be renamed to hasPrefix(_:),<br>
<br>
</span>+1<br>
<span class=""><br>
><br>
> and other APIs should have directional words replaced or added as<br>
> appropriate.<br>
><br>
> Additionally, the word drop in the "Exclude" APIs should be replaced<br>
> with removing. These operations omit the same elements which the<br>
> remove operations delete, so even though the types are not always the<br>
> same (removing returns SubSequence, not Self), I think they are<br>
> similar enough to deserve to be treated as nonmutating forms.<br>
><br>
> These changes yield (altered names bold):<br>
><br>
> Operand Get Index Exclude Remove (1) Pop (1) Equate (2)<br>
> Fixed Size<br>
> First 1 C.first - S.removingFirst() C.removeFirst() C.popFirst() -<br>
> Last 1 C.last - S.removingLast() C.removeLast() C.popLast() -<br>
> First (n: Int) S.prefix(_:) - S.removingPrefix(_:) C.removePrefix(_:) - S.hasPrefix(_:)<br>
</span>> ...with closure S.prefix(while:) - S.removingPrefix - - S.hasPrefix<br>
> (while:) (_:isEquivalent:)<br>
<br>
Call me overly fussy, but I don't love the use of “while” here because<br>
it seems stateful.<br>
<br>
xs.prefix(while: isFull)<br>
<br>
That reads like I'm going to repeatedly take the prefix of xs while some<br>
isFull property is true. The most descriptive usage I can think of is<br>
<br>
for x in xs.longestPrefix(where: isFull)<br>
<br>
What do you think?<br>
<br>
[BTW, you might need to stop using a table because it's already too<br>
wide, but your examples *really* ought to be showing use cases rather<br>
than signatures, c.f. the table in<br>
<a href="https://github.com/apple/swift/pull/2981" rel="noreferrer" target="_blank">https://github.com/apple/swift/pull/2981</a>. Otherwise it's hard]<br>
<span class=""><br>
> Last (n: Int) S.suffix(_:) - S.removingSuffix(_:) C.removeSuffix(_:) -<br>
</span>> - ...with closure - - - - - - Searching From End First matching -<br>
> C.earliestIndex(of:) - - - - element ...with closure<br>
> S.earliest(where:) C.earliestIndex - - - - (where:) Last matching<br>
<span class="">> element - - - - - - ...with closure - - - - - -<br>
><br>
</span>> SVG ImageAlternative to removing<br>
<span class="">><br>
> If the type differences are seen as disqualifying removing as a<br>
> replacement for drop,<br>
<br>
</span>They are not!<br>
<span class=""><br>
> I suggest using skipping instead.<br>
><br>
> There are, of course, many possible alternatives to skipping; this is<br>
> almost a perfect subject for bikeshedding. I've chosen skipping<br>
> because:<br>
><br>
</span>> 1 It is not an uncommon word, unlike (say) omitting. This means<br>
<span class="">> non-native English speakers and schoolchildren are more likely to<br>
> recognize it.<br>
><br>
</span>> 2 It is an -ing verb, unlike (say) without. This makes it fit common<br>
<span class="">> Swift naming patterns more closely.<br>
><br>
</span>> 3 It does not imply danger, unlike (say) dropping, nor some sort of<br>
<span class="">> ongoing process, unlike (say) ignoring. This makes its behavior more<br>
> obvious.<br>
><br>
> If you want to suggest an alternative on swift-evolution, please do<br>
> not merely mention a synonym; rather, explain why it is an improvement<br>
> on either these axes or other ones. (I would be particularly<br>
> interested in names other than removing which draw an analogy to<br>
> something else in Swift.)<br>
><br>
</span>> SVG ImageIndex-based operations<br>
<span class="">><br>
> 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<br>
> operations.<br>
><br>
> 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<br>
> present a more conservative alternative as well.<br>
><br>
</span>> SVG ImagePreferred (ambitious) option<br>
><br>
> let head = people[..<i]<br>
> let tail = people[i..<]<br>
<br>
let equivalentTail = people[i...] // reads a bit better, no?<br>
let headThroughI = people[...i]<br>
<br>
> let rearrangedPeople = tail + head<br>
><br>
> Or this small variation:<br>
><br>
> let head = people[nil ..< i]<br>
> let tail = people[i ..< nil]<br>
> let rearrangedPeople = tail + head<br>
><br>
> The operators would construct instances of a new pair of types,<br>
> IncompleteRange (for ..<) and IncompleteClosedRange (for ...), and<br>
> Collection would include new subscripts taking these types. These<br>
> would probably have default implementations which constructed an<br>
> equivalent Range or ClosedRange using startIndex and endIndex, then<br>
> passed the resulting range through to the existing subscripts.<br>
<br>
W00t!<br>
<br>
><br>
> I prefer this option because it offers an elegant syntax immediately<br>
> recognizable as a form of slicing, and provides a straightforward way<br>
> for a future version of Swift to extend other Range-handling<br>
> Collection operations, like replaceSubrange(_:with:) and<br>
> removeSubrange(_:), to handle subranges bound by the ends of the<br>
> Collection.<br>
<div class="HOEnZb"><div class="h5"><br>
--<br>
Dave<br>
<br>
_______________________________________________<br>
swift-evolution mailing list<br>
<a href="mailto:swift-evolution@swift.org">swift-evolution@swift.org</a><br>
<a href="https://lists.swift.org/mailman/listinfo/swift-evolution" rel="noreferrer" target="_blank">https://lists.swift.org/mailman/listinfo/swift-evolution</a><br>
</div></div></blockquote></div><br></div>