<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">&lt;<a href="mailto:swift-evolution@swift.org" target="_blank">swift-evolution@swift.org</a>&gt;</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 &lt;<a href="mailto:swift-evolution@swift.org" target="_blank">swift-evolution@swift.org</a>&gt; wrote:<br>
<br>
&gt; Hello Swift community,<br>
&gt;<br>
&gt; The review of &quot;SE-0132: Rationalizing Sequence end-operation names&quot;<br>
&gt; begins now and runs through July 26.  Apologies for the short review<br>
&gt; cycle, but we’re right up against the end of source breaking changes<br>
&gt; for Swift 3.  The proposal is available here:<br>
&gt;<br>
&gt;       <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>
&gt;<br>
&gt; Reviews are an important part of the Swift evolution process. All<br>
&gt; reviews should be sent to the swift-evolution mailing list at<br>
&gt;<br>
&gt;       <a href="https://lists.swift.org/mailman/listinfo/swift-evolution" rel="noreferrer" target="_blank">https://lists.swift.org/mailman/listinfo/swift-evolution</a><br>
&gt;<br>
&gt; or, if you would like to keep your feedback private, directly to the review manager.<br>
&gt;<br>
&gt; What goes into a review?<br>
&gt;<br>
&gt; The goal of the review process is to improve the proposal under review<br>
&gt; through constructive criticism and contribute to the direction of<br>
&gt; Swift. When writing your review, here are some questions you might<br>
&gt; want to answer in your review:<br>
&gt;<br>
&gt;       * What is your evaluation of the proposal?<br>
<br>
</span>I&#39;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&#39;m<br>
listed as a co-author (because of a small design contribution).  The<br>
only way I&#39;ve been able to read them is by looking at the markdown<br>
source, so that&#39;s how I&#39;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>
&gt; ## Proposed solution<br>
&gt;<br>
&gt; We sever the index-taking APIs from the others, forming two separate<br>
&gt; families, which we will call the &quot;Sequence-end operations&quot; and the<br>
&gt; &quot;index-based operations&quot;. We then consider and redesign them along<br>
&gt; separate lines.<br>
&gt;<br>
&gt; ### Sequence-end operations<br>
&gt;<br>
&gt; Each of these APIs should be renamed to use a directional word based on<br>
&gt; its row in the table:<br>
&gt;<br>
&gt; | Operand                          | Directional word   |<br>
&gt; | -------------------------------- | ------------------ |<br>
&gt; | **Fixed Size**                   |<br>
&gt; | First 1                          | first              |<br>
&gt; | Last 1                           | last               |<br>
&gt; | First (n: Int)                   | prefix             |<br>
&gt; |             ...with closure      | prefix             |<br>
&gt; | Last (n: Int)                    | suffix             |<br>
&gt; |             ...with closure      | suffix             |<br>
&gt; | **Searching From End**           |<br>
&gt; | First      matching      element | first              |<br>
&gt; |             ...with closure      | first              |<br>
&gt; | Last matching element            | last               |<br>
&gt; |             ...with closure      | last               |<br>
&gt;<br>
&gt; To accomplish this, `starts(with:)` should be renamed to<br>
&gt; `hasPrefix(_:)`, and other APIs should have directional words replaced<br>
&gt; or added as appropriate.<br>
&gt;<br>
&gt; Additionally, the word `drop` in the &quot;Exclude&quot; APIs should be replaced<br>
&gt; with `removing`. These operations omit the same elements which the<br>
&gt; `remove` operations delete, so even though the types are not always<br>
&gt; the same (`removing` returns `SubSequence`, not `Self`), we think they<br>
&gt; are similar enough to deserve to be treated as nonmutating forms.<br>
<br>
Unfortunately there&#39;s a semantic difference that I hadn&#39;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>
&gt; These changes yield (altered names **bold**):<br>
&gt;<br>
&gt; |                                  | Get                           | Index                                 | Exclude                                   | Remove (1)            | Pop (1)      | Equate (2)                                 |<br>
&gt; | -------------------------------- | ----------------------------- | ------------------------------------- | ----------------------------------------- | --------------------- | ------------ | ------------------------------------------ |<br>
&gt; | **Fixed Size**                   |<br>
&gt; | First 1                          | C.first                       | -                                     | **S.removingFirst()**                     | C.removeFirst()       | C.popFirst() | -                                          |<br>
&gt; | Last 1                           | C.last                        | -                                     | **S.removingLast()**                      | C.removeLast()        | C.popLast()  | -                                          |<br>
&gt; | First (n: Int)                   | S.prefix(3)                   | -                                     | **S.removingPrefix(3)**                   | **C.removePrefix(3)** | -            | **S.hasPrefix([x,y,z])**                   |<br>
&gt; |             ...with closure      | S.prefix(while:      isPrime) | -                                     | **S.removingPrefix(while:      isPrime)** | -                     | -            | **S.hasPrefix([x,y,z],      by:      ==)** |<br>
&gt; | Last (n: Int)                    | S.suffix(3)                   | -                                     | **S.removingSuffix(3)**                   | **C.removeSuffix(3)** | -            | -                                          |<br>
&gt; |             ...with closure      | -                             | -                                     | -                                         | -                     | -            | -                                          |<br>
&gt; | **Searching From End**           |<br>
&gt; | First      matching      element | -                             | **C.firstIndex(of:      x)**          | -                                         | -                     | -            | -                                          |<br>
&gt; |             ...with closure      | S.first(where:      isPrime)  | **C.firstIndex(where:      isPrime)** | -                                         | -                     | -            | -                                          |<br>
&gt; | Last matching element            | -                             | -                                     | -                                         | -                     | -            | -                                          |<br>
&gt; |             ...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(..&lt;i)<br>
<br>
   let i = x.index(x.endIndexIndex, offsetBy: -n, limitedBy: x.startIndex)<br>
   x.removeSubrange(i..&lt;)<br>
<br>
I realize that&#39;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..&lt;)<br>
   x.removeSubrange(..&lt;$-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&#39;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..&lt;]<br>
     x[..&lt;$-n]<br>
<br>
  for Collections.<br></blockquote><div><br></div><div>I&#39;m not enamored of this suggestion. It succeeds in reducing API surface area, but at a severe cost to readability. You&#39;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 [$+.&lt;]. 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 &quot;forgiving,&quot; while today&#39;s `dropLast` very much is; this suggestion would add inconsistency by using a subscript for a forgiving or &quot;lenient&quot; 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..&lt;$] and x[$..&lt;i]<br>
<br>
  which is after all only one character longer than<br>
<br>
     x[i..&lt;] and x[..&lt;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&#39;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">
&gt; ### Index-based operations<br>
&gt;<br>
&gt; Because these APIs look up elements based on their indices, we believe<br>
&gt; these operations should be exposed as subscripts, and ideally should<br>
&gt; look like other slicing operations:<br>
&gt;<br>
&gt; ```swift<br>
&gt; let head = people[..&lt;i]<br>
&gt; let tail = people[i..&lt;]<br>
&gt; let rearrangedPeople = tail + head<br>
&gt; ```<br>
&gt;<br>
&gt; &lt;!-- Comment to make my editor happy --&gt;<br>
&gt;<br>
&gt; We will accomplish this by introducing two new types, `IncompleteRange`<br>
&gt; and `IncompleteClosedRange`. These are similar to `Range` and<br>
&gt; `ClosedRange`, except that the bounds are optional.<br>
&gt;<br>
&gt; To construct them, we will introduce both prefix and suffix operators<br>
&gt; taking a non-optional bound, and infix operators taking optional bounds.<br>
&gt; (We offer both because `c[..&lt;i]` is more convenient than `c[nil ..&lt; i]`,<br>
&gt; but doesn&#39;t allow you to dynamically choose between supplying and<br>
&gt; omitting a bound.) These will follow the existing convention: `..&lt;` will<br>
&gt; construct the half-open `IncompleteRange`, while `...` will construct<br>
&gt; `IncompleteClosedRange`.<br>
<br>
I believe the `$+n..&lt;i` idea is still implementable with these basic<br>
types, just with an enum instead of optionals.  I&#39;ll take a shot at it<br>
tonight if I can get a few minutes.<br>
<br>
&gt; Rather than continuing to proliferate overloads of slicing subscripts,<br>
&gt; we will also introduce a new `RangeExpression` protocol which allows<br>
&gt; any range-like type to convert itself into a plain `Range&lt;Index&gt;`<br>
&gt; appropriate to the collection in question. Thus, there should only be<br>
&gt; two range subscripts: one taking `Range&lt;Index&gt;`, and one taking<br>
&gt; everything else.<br>
&gt;<br>
&gt; We will also modify the existing `removeSubrange(_:)` and<br>
&gt; `replaceSubrange(_:with:)` calls to take `RangeExpression` instances,<br>
&gt; thereby merging many existing variants into one while simultaneously<br>
&gt; extending them to support `IncompleteRange` and `IncompleteClosedRange`.<br>
&gt; Though technically additive, we believe this is an easy win.<br>
&gt;<br>
&gt; Thus, the table above becomes:<br>
&gt;<br>
&gt; |                                                  | Type                              | Get                  | Remove                              | Replace                                                     |<br>
&gt; | ------------------------------------------------ | --------------------------------- | -------------------- | ----------------------------------- | ----------------------------------------------------------- |<br>
&gt; | **Based      on      Index,      Arbitrary**     |<br>
&gt; | (i: Index) ..&lt; (j: Index)                        | Range\&lt;Index&gt;                     | C[i      ..&lt;      j] | C.removeSubrange(i      ..&lt;      j) | C.replaceSubrange(i      ..&lt;      j,      with:      [x,y]) |<br>
&gt; |             ...Countable                         | CountableRange\&lt;Index&gt;            | C[i ..&lt; j]           | C.removeSubrange(i      ..&lt;      j) | C.replaceSubrange(i      ..&lt;      j,      with:      [x,y]) |<br>
&gt; | (i: Index) ... (j: Index)                        | ClosedRange\&lt;Index&gt;               | C[i ... j]           | C.removeSubrange(i ... j)           | C.replaceSubrange(i ... j, with: [x,y])                     |<br>
&gt; |             ...Countable                         | CountableClosedRange\&lt;Index&gt;      | C[i ... j]           | C.removeSubrange(i ... j)           | C.replaceSubrange(i ... j, with: [x,y])                     |<br>
&gt; | **Based      on      Index,      From      End** |<br>
&gt; | startIndex ..&lt; (i: Index)                        | **IncompleteRange\&lt;Index&gt;**       | **C[..\&lt;i]**         | **C.removeSubrange(..\&lt;i)**         | **C.replaceSubrange(..\&lt;i,      with:      [x,y])**         |<br>
&gt; | (i: Index) ..&lt; endIndex                          | **IncompleteRange\&lt;Index&gt;**       | **C[i..\&lt;]**         | **C.removeSubrange(i..\&lt;)**         | **C.replaceSubrange(i..\&lt;, with: [x,y])**                   |<br>
&gt; | startIndex ... (i: Index)                        | **IncompleteClosedRange\&lt;Index&gt;** | **C[...i]**          | **C.removeSubrange(...i)**          | **C.replaceSubrange(...i, with: [x,y])**                    |<br>
&gt;<br>
&gt; However, it should be implemented with merely:<br>
&gt;<br>
&gt; |                                               | Type                                                | Get                  | Remove                              | Replace                                                     |<br>
&gt; | --------------------------------------------- | --------------------------------------------------- | -------------------- | ----------------------------------- | ----------------------------------------------------------- |<br>
&gt; | (i:      Index)      ..&lt;      (j:      Index) | Range\&lt;Index&gt;                                       | C[i      ..&lt;      j] | C.removeSubrange(i      ..&lt;      j) | C.replaceSubrange(i      ..&lt;      j,      with:      [x,y]) |<br>
&gt; | Everything else                               | RangeExpression where      Bound      ==      Index | C[i      ...      j] | C.removeSubrange(i      ...      j) | C.replaceSubrange(i      ...      j,      with:      [x,y]) |<br>
&gt;<br>
&gt; ## Detailed design<br>
&gt;<br>
&gt; ### Sequence-end operations<br>
&gt;<br>
&gt; The following methods should be renamed as follows wherever they appear<br>
&gt; in the standard library. These are simple textual substitutions; we<br>
&gt; propose no changes whatsoever to types, parameter interpretations, or<br>
&gt; other semantics.<br>
&gt;<br>
&gt; | Old method                                        | New method                                              |<br>
&gt; | ------------------------------------------------- | ------------------------------------------------------- |<br>
&gt; | `dropFirst() -&gt; SubSequence`                      | `removingFirst() -&gt; SubSequence`                        |<br>
&gt; | `dropLast() -&gt; SubSequence`                       | `removingLast() -&gt; SubSequence`                         |<br>
&gt; | `dropFirst(_ n: Int) -&gt; SubSequence`              | `removingPrefix(_ n: Int) -&gt; SubSequence`               |<br>
&gt; | `drop(@noescape while predicate: (Iterator.Element) throws -&gt; Bool) rethrows -&gt; SubSequence` | `removingPrefix(@noescape while predicate: (Iterator.Element) throws -&gt; Bool) rethrows -&gt; SubSequence` |<br>
<br>
I&#39;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)..&lt;$]<br>
<br>
and that&#39;s just for Collections.  Drat; we might not be able to get rid<br>
of these “removingPrefix” operations altogether.  OK, I&#39;m out of time so<br>
I&#39;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>