<div dir="ltr"><div class="gmail_extra"><div class="gmail_quote">On Mon, Jul 25, 2016 at 8:35 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><br>
on Sun Jul 24 2016, Chris Lattner <<a href="mailto:swift-evolution@swift.org" target="_blank">swift-evolution@swift.org</a>> wrote:<br>
<br>
> Hello Swift community,<br>
><br>
> The review of "SE-0132: Rationalizing Sequence end-operation names"<br>
> begins now and runs through July 26. Apologies for the short review<br>
> cycle, but we’re right up against the end of source breaking changes<br>
> for Swift 3. The proposal is available here:<br>
><br>
> <a href="https://github.com/apple/swift-evolution/blob/master/proposals/0132-sequence-end-ops.md" rel="noreferrer" target="_blank">https://github.com/apple/swift-evolution/blob/master/proposals/0132-sequence-end-ops.md</a><br>
><br>
> Reviews are an important part of the Swift evolution process. All<br>
> reviews should be sent to the swift-evolution mailing list at<br>
><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>
><br>
> or, if you would like to keep your feedback private, directly to the review manager.<br>
><br>
> What goes into a review?<br>
><br>
> The goal of the review process is to improve the proposal under review<br>
> through constructive criticism and contribute to the direction of<br>
> Swift. When writing your review, here are some questions you might<br>
> want to answer in your review:<br>
><br>
> * What is your evaluation of the proposal?<br>
<br>
</span>I'm mostly very much in favor of this proposal, but I have some<br>
thoughts.<br>
<br>
First, though, I have to apologize for those wide tables, since I'm<br>
listed as a co-author (because of a small design contribution). The<br>
only way I've been able to read them is by looking at the markdown<br>
source, so that's how I'm going to quote it here.<br>
<br>
[Note to future authors: if you need to include a table, this is how you<br>
can make it narrow enough:<br>
<a href="https://github.com/apple/swift-evolution/blob/master/proposals/0118-closure-parameter-names-and-labels.md#proposed-solution" rel="noreferrer" target="_blank">https://github.com/apple/swift-evolution/blob/master/proposals/0118-closure-parameter-names-and-labels.md#proposed-solution</a>.<br>
The source is awful to read but it renders OK.]<br>
<br>
<br>
> ## Proposed solution<br>
><br>
> We sever the index-taking APIs from the others, forming two separate<br>
> families, which we will call the "Sequence-end operations" and the<br>
> "index-based operations". We then consider and redesign them along<br>
> separate lines.<br>
><br>
> ### Sequence-end operations<br>
><br>
> Each of these APIs should be renamed to use a directional word based on<br>
> its row in the table:<br>
><br>
> | Operand | Directional word |<br>
> | -------------------------------- | ------------------ |<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 | first |<br>
> | ...with closure | first |<br>
> | Last matching element | last |<br>
> | ...with closure | last |<br>
><br>
> To accomplish this, `starts(with:)` should be renamed to<br>
> `hasPrefix(_:)`, and other APIs should have directional words replaced<br>
> or added as 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<br>
> the same (`removing` returns `SubSequence`, not `Self`), we think they<br>
> are similar enough to deserve to be treated as nonmutating forms.<br>
<br>
Unfortunately there's a semantic difference that I hadn't noticed<br>
before: the mutating “remove” operations have a precondition that there<br>
be at least as many elements as are being removed. “Drop,” like “pop,”<br>
is forgiving of such overruns. I think this is solvable; my suggestion<br>
is below<br>
<br>
> These changes yield (altered names **bold**):<br>
><br>
> | | Get | Index | Exclude | Remove (1) | Pop (1) | Equate (2) |<br>
> | -------------------------------- | ----------------------------- | ------------------------------------- | ----------------------------------------- | --------------------- | ------------ | ------------------------------------------ |<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(3) | - | **S.removingPrefix(3)** | **C.removePrefix(3)** | - | **S.hasPrefix([x,y,z])** |<br>
> | ...with closure | S.prefix(while: isPrime) | - | **S.removingPrefix(while: isPrime)** | - | - | **S.hasPrefix([x,y,z], by: ==)** |<br>
> | Last (n: Int) | S.suffix(3) | - | **S.removingSuffix(3)** | **C.removeSuffix(3)** | - | - |<br>
> | ...with closure | - | - | - | - | - | - |<br>
> | **Searching From End** |<br>
> | First matching element | - | **C.firstIndex(of: x)** | - | - | - | - |<br>
> | ...with closure | S.first(where: isPrime) | **C.firstIndex(where: isPrime)** | - | - | - | - |<br>
> | Last matching element | - | - | - | - | - | - |<br>
> | ...with closure | - | - | - | - | - | - |<br>
<br>
My suggestion would be to make the remove()<br>
operations more forgiving:<br>
<br>
rename popFirst() to removeFirst()<br>
rename popLast() to removeLast()<br>
<br>
kill removeFirst(n)<br>
kill removeLast(n)<br></blockquote><div><br></div><div>+1 to this. </div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
The “forgiving” forms of x.removeFirst(n) and x.removeLast(n) can be<br>
expressed as:<br>
<br>
let i = x.index(x.startIndex, offsetBy: n, limitedBy: x.endIndex)<br>
x.removeSubrange(..<i)<br>
<br>
let i = x.index(x.endIndexIndex, offsetBy: -n, limitedBy: x.startIndex)<br>
x.removeSubrange(i..<)<br>
<br>
I realize that's quite verbose. We could of course just make<br>
removePrefix(n) and removeSuffix(n) forgiving, but I have long believed<br>
that the “prefix/suffix” methods should go one of two ways:<br>
<br>
a. they get a label that clarifies the meaning of the argument, e.g.<br>
<br>
x.removePrefix(ofMaxLength: n)<br>
x.removeSuffix(ofMaxLength: n)<br>
<br>
b. they are given a recognizable domain-specific notation such as:<br>
<br>
x.removeSubrange($+n..<)<br>
x.removeSubrange(..<$-n)<br>
<br>
I am strongly in favor of this answer (which is implementable within<br>
the framework of this proposal) because of the way it reduces API<br>
surface area and leverages the user's understanding of how ranges<br>
work.<br>
<br>
It also implies we can replace<br>
<br>
x.removingPrefix(n)<br>
x.removingSuffix(n)<br>
<br>
with<br>
<br>
x[$+n..<]<br>
x[..<$-n]<br>
<br>
for Collections.<br></blockquote><div><br></div><div>I'm not enamored of this suggestion. It succeeds in reducing API surface area, but at a severe cost to readability. You'd replace an unambiguous phrase (removing prefix or suffix), the meaning of which is further clarified by the consistent usage proposed in SE-0132, with a wordless spelling using some combination of [$+.<]. Cognitively, also, it substantially increases the burden for the reader: it replaces a single argument with a nested series of arguments; first, one must understand the meaning of the $ placeholder, then one must consider an addition or subtraction operation, then the formation of a range, and in the last example, the use of that range as a subscript argument--again, all wordlessly. Finally, subscripts have so far not been "forgiving," while today's `dropLast` very much is; this suggestion would add inconsistency by using a subscript for a forgiving or "lenient" operation.</div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
That would admittedly leave single-pass Sequences without an API for<br>
dropping the first N elements. I am inclined to think that interface<br>
should be moved to Iterator.<br>
<br>
The introduction of such notation raises the question of whether we<br>
need unary range operators, and could instead go with<br>
<br>
x[i..<$] and x[$..<i]<br>
<br>
which is after all only one character longer than<br>
<br>
x[i..<] and x[..<i]<br>
<br>
and stays out of the territory of the prefix/suffix “pack/unpack”<br>
operators that are likely to be used for generic variadics.<br></blockquote><div><br></div><div>Pyry's comments about the precedence issues with unary range operators are, I think, serious enough that the unary operators should be reconsidered. This suggestion might work but I wonder if we could do better than $.</div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
> ### Index-based operations<br>
><br>
> Because these APIs look up elements based on their indices, we believe<br>
> these operations should be exposed as subscripts, and ideally should<br>
> look like other slicing operations:<br>
><br>
> ```swift<br>
> let head = people[..<i]<br>
> let tail = people[i..<]<br>
> let rearrangedPeople = tail + head<br>
> ```<br>
><br>
> <!-- Comment to make my editor happy --><br>
><br>
> We will accomplish this by introducing two new types, `IncompleteRange`<br>
> and `IncompleteClosedRange`. These are similar to `Range` and<br>
> `ClosedRange`, except that the bounds are optional.<br>
><br>
> To construct them, we will introduce both prefix and suffix operators<br>
> taking a non-optional bound, and infix operators taking optional bounds.<br>
> (We offer both because `c[..<i]` is more convenient than `c[nil ..< i]`,<br>
> but doesn't allow you to dynamically choose between supplying and<br>
> omitting a bound.) These will follow the existing convention: `..<` will<br>
> construct the half-open `IncompleteRange`, while `...` will construct<br>
> `IncompleteClosedRange`.<br>
<br>
I believe the `$+n..<i` idea is still implementable with these basic<br>
types, just with an enum instead of optionals. I'll take a shot at it<br>
tonight if I can get a few minutes.<br>
<br>
> Rather than continuing to proliferate overloads of slicing subscripts,<br>
> we will also introduce a new `RangeExpression` protocol which allows<br>
> any range-like type to convert itself into a plain `Range<Index>`<br>
> appropriate to the collection in question. Thus, there should only be<br>
> two range subscripts: one taking `Range<Index>`, and one taking<br>
> everything else.<br>
><br>
> We will also modify the existing `removeSubrange(_:)` and<br>
> `replaceSubrange(_:with:)` calls to take `RangeExpression` instances,<br>
> thereby merging many existing variants into one while simultaneously<br>
> extending them to support `IncompleteRange` and `IncompleteClosedRange`.<br>
> Though technically additive, we believe this is an easy win.<br>
><br>
> Thus, the table above becomes:<br>
><br>
> | | Type | Get | Remove | Replace |<br>
> | ------------------------------------------------ | --------------------------------- | -------------------- | ----------------------------------- | ----------------------------------------------------------- |<br>
> | **Based on Index, Arbitrary** |<br>
> | (i: Index) ..< (j: Index) | Range\<Index> | C[i ..< j] | C.removeSubrange(i ..< j) | C.replaceSubrange(i ..< j, with: [x,y]) |<br>
> | ...Countable | CountableRange\<Index> | C[i ..< j] | C.removeSubrange(i ..< j) | C.replaceSubrange(i ..< j, with: [x,y]) |<br>
> | (i: Index) ... (j: Index) | ClosedRange\<Index> | C[i ... j] | C.removeSubrange(i ... j) | C.replaceSubrange(i ... j, with: [x,y]) |<br>
> | ...Countable | CountableClosedRange\<Index> | C[i ... j] | C.removeSubrange(i ... j) | C.replaceSubrange(i ... j, with: [x,y]) |<br>
> | **Based on Index, From End** |<br>
> | startIndex ..< (i: Index) | **IncompleteRange\<Index>** | **C[..\<i]** | **C.removeSubrange(..\<i)** | **C.replaceSubrange(..\<i, with: [x,y])** |<br>
> | (i: Index) ..< endIndex | **IncompleteRange\<Index>** | **C[i..\<]** | **C.removeSubrange(i..\<)** | **C.replaceSubrange(i..\<, with: [x,y])** |<br>
> | startIndex ... (i: Index) | **IncompleteClosedRange\<Index>** | **C[...i]** | **C.removeSubrange(...i)** | **C.replaceSubrange(...i, with: [x,y])** |<br>
><br>
> However, it should be implemented with merely:<br>
><br>
> | | Type | Get | Remove | Replace |<br>
> | --------------------------------------------- | --------------------------------------------------- | -------------------- | ----------------------------------- | ----------------------------------------------------------- |<br>
> | (i: Index) ..< (j: Index) | Range\<Index> | C[i ..< j] | C.removeSubrange(i ..< j) | C.replaceSubrange(i ..< j, with: [x,y]) |<br>
> | Everything else | RangeExpression where Bound == Index | C[i ... j] | C.removeSubrange(i ... j) | C.replaceSubrange(i ... j, with: [x,y]) |<br>
><br>
> ## Detailed design<br>
><br>
> ### Sequence-end operations<br>
><br>
> The following methods should be renamed as follows wherever they appear<br>
> in the standard library. These are simple textual substitutions; we<br>
> propose no changes whatsoever to types, parameter interpretations, or<br>
> other semantics.<br>
><br>
> | Old method | New method |<br>
> | ------------------------------------------------- | ------------------------------------------------------- |<br>
> | `dropFirst() -> SubSequence` | `removingFirst() -> SubSequence` |<br>
> | `dropLast() -> SubSequence` | `removingLast() -> SubSequence` |<br>
> | `dropFirst(_ n: Int) -> SubSequence` | `removingPrefix(_ n: Int) -> SubSequence` |<br>
> | `drop(@noescape while predicate: (Iterator.Element) throws -> Bool) rethrows -> SubSequence` | `removingPrefix(@noescape while predicate: (Iterator.Element) throws -> Bool) rethrows -> SubSequence` |<br>
<br>
I'm concerned with how the above fits into the scheme. Writing it out<br>
is:<br>
<br>
x[(x.firstIndex(where: {!predicate($0)}) ?? x.endIndex)..<$]<br>
<br>
and that's just for Collections. Drat; we might not be able to get rid<br>
of these “removingPrefix” operations altogether. OK, I'm out of time so<br>
I'll have to get back to this.<br>
<span><font color="#888888"><br>
<br>
--<br>
Dave<br>
</font></span><div><div><br>
_______________________________________________<br>
swift-evolution mailing list<br>
<a href="mailto:swift-evolution@swift.org" target="_blank">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></div>