[swift-evolution] [Draft] Rationalizing Sequence end-operation names
Nevin Brackett-Rozinsky
nevin.brackettrozinsky at gmail.com
Sun Jul 3 10:25:35 CDT 2016
The incomplete range concept is quite intriguing.
Have we considered spelling the operators with an asterisk at the
incomplete end?
prefix *..<
prefix *...
postfix ...*
postfix ..<*
That way the use-sites would look like:
someCollection[*..<idx]
someCollection[*...idx]
someCollection[idx...*]
someCollection[idx..<*]
>From a “first-glance” perspective, the asterisk “looks like” a wildcard
placeholder, which should help readers and writers of code to understand
the meaning.
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.)
Thoughts?
Nevin
On Fri, Jul 1, 2016 at 6:50 PM, Dave Abrahams via swift-evolution <
swift-evolution at swift.org> wrote:
>
> on Thu Jun 23 2016, Brent Royal-Gordon <swift-evolution at swift.org> wrote:
>
> > 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:)`
>
> What's wrong with firstIndex(of:/where:) [and lastIndex(of:/where:)]?
> That seems like a much less esoteric way to phrase it that meshes well
> with the meanings of
>
> xs.first
> xs.indices.first
>
> etc.
>
> > • Renaming the `drop` methods to use `removing`
>
> Very clever! I *like*.
>
> > • Redesigning `prefix(upTo:)`, `prefix(through:)` and `suffix(from:)`
> > as subscripts with "partial" ranges, like `people[..<idx]` or perhaps
> > `people[nil..<idx]`.
>
> Yes please; I really want this. This part is a slightly nontrivial
> design problem, though. Someone should build an implementation before
> the actual design is proposed. Probably the best way would be to
> leave prefix and suffix alone for the moment and add/test the new
> subscripts.
>
> > 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.
>
> I really don't like using labels for this, because stride(to:) and
> stride(through:) have already spawned a naming bikeshed with no clear
> resolution, suggesting that no name works. Plus, the ..< operator
> already implies the name.
>
> > 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.
>
> Good.
>
> > I would mainly like feedback on the two most open questions left in
> > this proposal:
> >
> > • The choice of `removing` to replace `drop`
>
> It's 100% appropriate, provided that the APIs match some corresponding
> mutating remove API. Nonmutating operations are often implemented via
> lazy adaptors... which a slice can be viewed to be. So I think this is
> a beautiful answer.
>
> > • The decision about whether to use `people[..<idx]`,
> > `people[nil..<idx]`, or `people[to: idx]`.
>
> I prefer how the first one reads.
>
> > 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 very much appreciate that you're addressing all of these at once.
>
> > 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.)
> >
> > 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)
>
> I think you want “Operation” or “Semantics” rather than “Operand” (which
> means an argument to an operation)
>
> > 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 - C.index(of:) - - - -
> > element
> > ...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:
> >
> > 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
> > checks whether or not they exist.
> >
> > 2 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.
> >
> > SVG ImageInconsistent 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.
> >
> > SVG Imagefirst 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.
>
> You seem to be suggesting that a word needs to mean exactly the same
> thing regardless of context. If so, I disagree. If I say “the first
> element” or “the first element greater than 5” there's absolutely no
> lack of clarity AFAICT. That accounts for the first and last bullets
>
> The usage in the middle bullet is open to misinterpretation and I would
> support fixing that.
>
> xs.removeFirst(42)
>
> could read like, “remove the first element equal to 42.”
>
> > SVG Imagedrop 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;
>
> Tatoo that on your forehead, mister!
>
> > 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.
> >
> > SVG ImageUnstated 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 would add lastIndex methods.)
> >
> > SVG ImageThe 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.
>
> There's another relationship. Once you call the high-level operation,
> you're now in the domain of indexing, and are very likely to ask for the
> index(after:) the one you found.
>
> > It would be nice to separate these two groups of methods into
> > different families.
>
> I used to think that was important, but I no longer do given the above.
>
> > SVG ImageOperations 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.
>
> Yes please.
>
> > SVG ImageWhy 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.
> >
> > SVG ImageProposed 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.
> >
> > SVG ImageSequence-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(_:),
>
> +1
>
> >
> > 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 - - S.hasPrefix
> > (while:) (_:isEquivalent:)
>
> Call me overly fussy, but I don't love the use of “while” here because
> it seems stateful.
>
> xs.prefix(while: isFull)
>
> That reads like I'm going to repeatedly take the prefix of xs while some
> isFull property is true. The most descriptive usage I can think of is
>
> for x in xs.longestPrefix(where: isFull)
>
> What do you think?
>
> [BTW, you might need to stop using a table because it's already too
> wide, but your examples *really* ought to be showing use cases rather
> than signatures, c.f. the table in
> https://github.com/apple/swift/pull/2981. Otherwise it's hard]
>
> > Last (n: Int) S.suffix(_:) - S.removingSuffix(_:) C.removeSuffix(_:) -
> > - ...with closure - - - - - - Searching From End First matching -
> > C.earliestIndex(of:) - - - - element ...with closure
> > S.earliest(where:) C.earliestIndex - - - - (where:) Last matching
> > element - - - - - - ...with closure - - - - - -
> >
> > SVG ImageAlternative to removing
> >
> > If the type differences are seen as disqualifying removing as a
> > replacement for drop,
>
> They are not!
>
> > 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:
> >
> > 1 It is not an uncommon word, unlike (say) omitting. This means
> > non-native English speakers and schoolchildren are more likely to
> > recognize it.
> >
> > 2 It is an -ing verb, unlike (say) without. This makes it fit common
> > Swift naming patterns more closely.
> >
> > 3 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.)
> >
> > SVG ImageIndex-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.
> >
> > SVG ImagePreferred (ambitious) option
> >
> > let head = people[..<i]
> > let tail = people[i..<]
>
> let equivalentTail = people[i...] // reads a bit better, no?
> let headThroughI = 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.
>
> W00t!
>
> >
> > 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.
>
> --
> Dave
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160703/a448d11c/attachment.html>
More information about the swift-evolution
mailing list