[swift-evolution] [Draft] Rationalizing Sequence end-operation names
Dave Abrahams
dabrahams at apple.com
Mon Jul 11 13:04:22 CDT 2016
on Fri Jul 08 2016, Brent Royal-Gordon <brent-AT-architechies.com> wrote:
>> 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.
Well, it's a reasonable thing to want to distinguish, but I don't think
I want to burden every API that effectively does a linear search with
the awkwardness of “earliest” and “latest.” To me it doesn't look like
a great trade-off.
We could rename “first” and “last” so they always mean O(N) but that
seems overly fussy too.
Just IMO, of course.
>>> 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".
I know that, and that's exactly why “Operand” is the wrong word here.
> 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.)
Indeedy.
>>> 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.)
+1
>
>
>>> 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:)`—
Well, I'm really hoping we won't hang on to the first one forever, and
that slicing syntax will eventually work here.
> unless we change it for all of the relevant calls, but I'm certainly
> open to changing the label. Maybe `prefix(whereEach: isFull)`?
I think “while” is better than that one, since it implies “longest.”
However, I still think “longestPrefix...” will read much more clearly at
the call site.
> On the other hand, does `xs.prefix(3)` read well?
One among many reasons I want to use slicing syntax here.
> APIs that take counts seem to be challenging to name; I've had some of
> the same problems with UnsafeRawPointer APIs.
A separate proposal to clean up APIs taking counts might be a good idea,
then. But let's not get too far afield in this thread.
>> [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.
Ah, you're right, that's a dimension of simplicity I hadn't considered
(could I have said that in a more complicated way?!)
> Like the `..<` operator itself, I think we're better off with ugly
> clarity than pretty vagueness.
--
Dave
More information about the swift-evolution
mailing list