[swift-evolution] [Draft] Rationalizing Sequence end-operation names
Brent Royal-Gordon
brent at architechies.com
Fri Jul 8 21:22:07 CDT 2016
> On Jul 1, 2016, at 3:50 PM, Dave Abrahams via swift-evolution <swift-evolution at swift.org> wrote:
>> • 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.
I'll try to figure out how to wedge something into stdlib, but until I do, here's something I tried out in a playground: https://gist.github.com/brentdax/b36ef130873b752d4c6f7ee3c157d07d
(Already sent that to you, Dave; this is for everyone else.)
>> • 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.
[and]
>> 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.
>
> 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
I was hoping to distinguish between the O(1), always-anchored first/last calls and the O(n), unanchored earliest/latest calls. Perhaps that isn't necessary, though; `xs.first(x)` *does* read well, and it'd be difficult to imagine an implementation on most collections that didn't involve searching multiple elements.
>> 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)
"Operand" is meant to label the column below it, which lists things like "First 1 Element". Maybe I should just leave that cell blank, though.
>> The term-of-art exception is not a suicide pact;
>
> Tatoo that on your forehead, mister!
Touché.
(I still believe map, reduce, and filter are much stronger terms of art and don't require much modification, but that's a discussion for another thread.)
>> 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.
>
> 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.
Maybe. I suspect most users simply use the index without manipulating it, but certainly you'll sometimes use both.
(But even without that, I still think the directional vagueness and the possibility of a `lastIndex` method in the future are good enough justifications to rename it on their own.)
>> 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?
I don't like changing the base name—it breaks the connection between `prefix(_:)` and `prefix(while:)`—unless we change it for all of the relevant calls, but I'm certainly open to changing the label. Maybe `prefix(whereEach: isFull)`?
On the other hand, does `xs.prefix(3)` read well? APIs that take counts seem to be challenging to name; I've had some of the same problems with UnsafeRawPointer APIs.
> [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]
I'll try to find a way to fit examples in.
>> Preferred (ambitious) option
>>
>> let head = people[..<i]
>> let tail = people[i..<]
>
> let equivalentTail = people[i...] // reads a bit better, no?
> let headThroughI = people[...i]
It looks nicer, but it breaks the mental model of these unary forms merely filling in `startIndex` or `endIndex` automatically. Like the `..<` operator itself, I think we're better off with ugly clarity than pretty vagueness.
--
Brent Royal-Gordon
Architechies
More information about the swift-evolution
mailing list